Cypress JS using multiple page objects in one method - javascript

i have been struggling with some logic about using multiple page objects in one method, i couldnt find any idea to use like that logic, for example;
These are my methods in my page object called usersTable;
get rolesAndStatusMenu() {
return cy.get("#menu- > .MuiPaper-root > .MuiMenu-list>li");
}
get usersPartialRow() {
return cy.get(".MuiTableBody-root>tr>td");
}
settings(options: string) {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains(options);
}
menuButton(userId: string) {
return cy.get(`.user_${userId}>td>button`);
}
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow).get(`.user_${userId}`);
}
im using userRow method in this test like that;
usersTable.userRow(userId).should("not.exist");
And for exaple im using my userMenu and settings method in this test;
usersTable.menuButton(userId).click();
usersTable.settings("Impersonate").click();
Let's come to the idea that I want to do but I can't find the way to do it;
usersTable.userRow(userId).settings.menuButton.click()
usersTable.userRow(userId).settings.impersonate.click()
Is there a any way to use like that ? All ideas are accepted
Update
I have one more page object, i define my usersTable component modal inside called usersPage page modal
import { UsersTable } from "../components/UsersTable ";
export class Users {
visit() {
return cy.visit("/users");
}
get headingText() {
return cy.get(".MuiTypography-h5");
}
get inviteUserBtn() {
return cy.get(".MuiGrid-root> .MuiButtonBase-root");
}
get inviteUserModal() {
return cy.get(".MuiDialogContent-root");
}
get usersTable() {
return new UsersTable();
}
}
So my code looks like this
usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Impersonate").click();
usersPage.visit();
usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Delete").click();
usersPage.usersTable.userRow(userId).should("not.exist");
For example using this way
usersPage.usersTable.userRow(userId).settings.menuButton.click()
So maybe i can create class inside UsersTable
export class UsersTable {
...
}
class userTableRow {
}
**and returning it in `UsersTable` or something like that ?**
Second Update
Now i create a class inside UsersTable file;
class UserRow {
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow).find(`.user_${userId}`);
}
get menuButton() {
return this.userRow(`>td>button`); //Btw im not sure this one is working, i think something is wrong here;
}
get impersonate() {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains("Impersonate");
}
get delete() {
return cy
.get(
"[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
)
.contains("Delete");
}
}
And for using this class returned in UsersTable class;
userRow(userId?: string) {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return new UserRow(userId); **// but got error, it says Expected 0 arguments, but got 1.**
}
If i use like this comment section;
// get UserRow() {
// return new UserRow();
// }
I can able to reach everything inside user but i can't use my test like this;
usersPage.usersTable.UserRow(userId).settings.menuButton.click()
or maybe
usersPage.usersTable.UserRow.userRow(userId).settings.menuButton.click()
But i can use like this;
usersPage.usersTable.UserRow.menuButton.click()
How can i define userId?: string for UserRow userId is constantly changing every time, I get it from API inside test, So I can't define for sure.

To make class methods fluid, you will need to return this.
class UsersTable {
settings(options: string) {
cy.get(...).contains(options);
return this // call next method on return value
}
menuButton(userId: string) {
return cy.get(`.user_${userId}>td>button`); // cannot call another after this one
}
userRow(userId?: string) {
cy.get(userrow).get(`.user_${userId}`);
return this; // call next method on return value
}
}
Should work,
usersTable.userRow(userId).settings().menuButton().click()
But now, where are the values from first two methods?
You would need to store them in the class
class UsersTable {
userId: string = '';
setting: string = '';
settings(options: string) {
cy.get(...).contains(options)
.invoke('text')
.then(text => this.setting = text)
return this
}
menuButton() { // no parameter, take value from field
return cy.get(`.user_${this.setting}>td>button`);
}
userRow() { // no parameter, take value from field
cy.get(userrow).get(`.user_${this.userId}`)
.invoke('text')
.then(text => this.userId = text)
return this; // call next method on return value
}
}
But now have lost flexibility, methods are tightly coupled, not independent anymore.

The UserTable methods return the Cypress.Chainable type, so you will need to unwrap the result to pass it to the next method.
Also, it's returning the element but next method needs text content, so extract that as well.
usersTable.userRow(userId)
.then((userIdElement: JQuery<HTMLElement>) => { // unwrap Chainable
const text = userIdElement.text() // extract text
usersTable.settings(text)
})
.then((settingsElement: JQuery<HTMLElement>) => { // unwrap Chainable
const text = settingsElement.text() // extract text
usersTable.menuButton(text).click()
})
If any of the elements are HTMLInputElement, use userIdElement.val() instead.
An adjustment to userRow():
class UsersTable {
...
userRow(userId?: string): Cypress.Chainable<JQuery<HTMLElement>> {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow)
.find(`.user_${userId}`) // use find instead of get
}
}
How to do it with Custom Commands instead of pageObject
Chaining is a natural code pattern for Cypress commands, so using Custom Commands
commands.js
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject = any> {
settings(options?: string): Chainable<JQuery<HTMLElement>>;
menuButton(userId?: string): Chainable<JQuery<HTMLElement>>;
userRow(userId?: string): Chainable<JQuery<HTMLElement>>;
}
}
Cypress.Commands.add('settings', {prevSubject: 'optional'}, (subject: any, options?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
if (options === undefined) {
options = subject as string;
}
return cy.get("[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root")
.contains(options)
})
Cypress.Commands.add('menuButton', {prevSubject: 'optional'}, (subject: any, userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
if (userId === undefined) {
userId = subject as string;
}
return cy.get(`.user_${userId}>td>button`);
})
Cypress.Commands.add('userRow', (userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
const userrow = ".MuiTableBody-root>tr";
if (userId === undefined) {
return cy.get(userrow);
}
return cy.get(userrow)
.find(`.user_${userId}`)
})
test
it('tests with userId from userRow()', () => {
const userId = '1'
cy.userRow(userId)
.settings() // as child command, userId from previous command
.menuButton()
.click()
});
it('tests with userId hard-coded', () => {
cy.settings('abc') // as parent command, userId passed as parameter
.menuButton()
.click()
});

Related

How to define a name of a callback function properly as argument, if the callback name is defined in other class

This is a bit of a mock-up: I have test class and columnHeader class. The test class calls a function with a loop through all the table columns on the table. For each column, I would like to apply either filtering or a sorting.
With the following code my problem is, that eslint complains:
let columnHeader: ColumnHeader Avoid referencing unbound methods which
may cause unintentional scoping of this.
and even more the variable tableNam is not defined in the callback functions (clickColumnActionFilter,clickColumnSort).
The test file:
import { ColumnHeader } from '../../../support/spa/ColumnHeader';
export function tableColumnActionTest() {
describe(`Table column sorting and filtering`, () => {
let columnHeader = new ColumnHeader();
before(() => {
A_page.visit(//some url);
columnHeader = new ColumnHeader();
columnHeader.setTableN('aTable');
});
it.only('Select column action tests', () => {
columnHeader.loopThroughColumnHeader(columnHeader.clickColumnActionFilter);
});
});
}
The class file:
import { table } from './Table';
interface Action {
(colId: string): void;
}
export class ColumnHeader {
tableNam: string;
setTableN(tabN: string) {
this.tableNam = tabN;
}
clickColumnSort(colName: string) {
cy.log(`tableName: ${this.tableNam}`);
// do sorting
}
clickColumnActionFilter(colName: string) {
cy.log(`tableName: ${this.tableNam}`);
// do filtering
}
loopThroughColumnHeader(columnAction: Action): void {
for (let i = 1; i <= 10; i++) {
table.getColumnIdFromNr(i);
cy.get('#COLID').then(($colIDent) => {
const colId = $colIDent as unknown as string;
columnAction(colId);
});
cy.wait(500);
}
});
table.getColumnNameFromNr(2);
}
}
export const columnHeader = new ColumnHeader();
Do you know how to call the function name as argument properly so that the environment is correct?
Ok I found out:
In the test file, I needed to bind the columnheader.
This solved the eslint complaint and the undefined member variable
columnHeader.loopThroughColumnHeader(columnHeader.clickColumnActionFilter.bind(columnHeader));
instead of just:
columnHeader.loopThroughColumnHeader(columnHeader.clickColumnActionFilter)

code coverage doesn't reach void set function

Code Coverage doesn't reach some lines of codes even though I'm testing them. :(
Here is bind.ts decorator and [[NOT COVERED]] code coverage tag I created. Currently the set(value) is not covered by the test even though I'm covering it.
type Descriptor<T> = TypedPropertyDescriptor<T>;
export default function bind<T = Function>(
target: object,
key: string,
descriptor: Descriptor<T>
): Descriptor<T> {
...
set(value: T): void {
[[22-NOT COVERED]] if (process.env.NODE_ENV !== 'test') {
[[23-NOT COVERED]] throw new Error('Unable to set new value to decorated method');
[[24-NOT COVERED]] }
[[25-NOT COVERED]] Object.defineProperty(this, key, { ...descriptor, value });
},
};
}
bind.spec.ts
My strategy is to create new class Component and test its context on call
class MockClass extends React.PureComponent<Props, State> {
#bind
getProp(): string {
const { propName } = this.props;
return propName;
}
#bind
getState(): string {
const { stateName } = this.state;
return stateName;
}
#bind
setProp(value: string): void {
this.state = { stateName: value };
}
}
...
describe('bind', () => {
const mockState = {
stateName: 'stateName',
};
const mockProp = {
propName: 'propName',
};
const mockClass = new MockClass(mockProp, mockState);
...
it('should have called it once', () => {
expect(mockClass.getProp()).toBe(mockProp.propName);
});
it('should have called it in setState', () => {
expect(mockClass.setProp('newState')).toBe(undefined); <<<- This can cover 22-25??
});
The uncovered setter is code that would be exercised if you set a value of the class property. You don't have any test code that does this. You're only getting a property named setProp then calling it. The fact that the property has "set" in its name may be confusing matters.
Your test code would have to do something like this to test the setter of the decorator:
mockClass.props.otherPropName = 'blah';
mockClass.getProp = function() {
const { otherPropName } = this.props;
return otherPropName;
};
expect(mockClass.getProp()).toEqual('blah');

Observable subject event listener

I was looking into Observables and their differences to EventEmitter and then stumbled upon Subjects ( which I can see Angulars EventEmitter is based off ).
It seems Observables are unicast vs Subjects that are multicast ( and then an EE is simply a subject that wraps .next in emit to give the correct interface ).
Observables seem easy enough to implement
class Observable {
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(next, complete, error) {
const observer = new Observer(next, complete, error);
// return way to unsubscribe
return this._subscribe(observer);
}
}
Where Observer is just a wrapper that adds some try catches and monitors isComplete so it can clean up and stop observing.
For a Subject I came up with:
class Subject {
subscribers = new Set();
constructor() {
this.observable = new Observable(observer => {
this.observer = observer;
});
this.observable.subscribe((...args) => {
this.subscribers.forEach(sub => sub(...args))
});
}
subscribe(subscriber) {
this.subscribers.add(subscriber);
}
emit(...args) {
this.observer.next(...args);
}
}
which sort of merges into an EventEmitter with it wrapping .next with emit - but capturing the observe argument of the Observable seems wrong - and like I have just hacked up a solution. What would be the better way to produce a Subject (multicast) from an Observable (unicast)?
I tried looking at RXJS but I can't see how it's subscribers array ever gets populated :/
I think you can have a better understanding by using the debugger as well. Open a StackBlitz RxJS project, create the simplest example(depending on what you're trying to understand) and then place some breakpoints. AFAIK, with StackBlitz you can debug the TypeScript files, which seems great.
Firstly, the Subject class extends Observable:
export class Subject<T> extends Observable<T> implements SubscriptionLike { /* ... */ }
Now let's examine the Observable class.
It has the well-known pipe method:
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
where pipeFromArray is defined as follows:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
Before clarifying what's going on in the above snippet, it is important to know that operators are. An operator is a function which returns another function whose single argument is an Observable<T> and whose return type is an Observable<R>. Sometimes, T and R can be the same(e.g when using filter(), debounceTime()...).
For example, map is defined like this:
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate((source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
export function operate<T, R>(
init: (liftedSource: Observable<T>, subscriber: Subscriber<R>) => (() => void) | void
): OperatorFunction<T, R> {
return (source: Observable<T>) => {
if (hasLift(source)) {
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
}
So, operate will return a function. Notice its argument: source: Observable<T>. The return type is derived from Subscriber<R>.
Observable.lift just creates a new Observable. It's like creating nodes within a liked list.
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
// it's important to keep track of the source !
observable.source = this;
observable.operator = operator;
return observable;
}
So, an operator(like map) will return a function. What invokes that function is the pipeFromArray function:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
// here the functions returned by the operators are being called
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
In the above snippet, fn is what the operate function returns:
return (source: Observable<T>) => {
if (hasLift(source)) { // has `lift` method
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
Maybe it would be better to see an example as well. I'd recommend trying this yourself with a debugger.
const src$ = new Observable(subscriber => {subscriber.next(1), subscriber.complete()});
The subscriber => {} callback fn will be assigned to the Observable._subscribe property.
constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Next, let's try adding an operator:
const src2$ = src$.pipe(map(num => num ** 2))
In this case, it will invoke this block from pipeFromArray:
// `pipeFromArray`
if (fns.length === 1) {
return fns[0];
}
// `Observable.pipe`
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
So, the Observable.pipe will invoke (source: Observable<T>) => { ... }, where source is the src$ Observable. By invoking that function(whose result is stored in src2$), it will also call the Observable.lift method.
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
/* ... */
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
observable.source = this;
observable.operator = operator;
return observable;
}
At this point, src$ is an Observable instance, which has the source set to src$ and the operator set to function (this: Subscriber<R>, liftedSource: Observable<T>) ....
From my perspective, it's all about linked lists. When creating the Observable chain(by adding operators), the list is created from top to bottom.
When the tail node has its subscribe method called, another list will be created, this time from bottom to top. I like to call the first one the Observable list and the second one the Subscribers list.
src2$.subscribe(console.log)
This is what happens when the subscribe method is called:
const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
const { operator, source } = this;
subscriber.add(
operator
? operator.call(subscriber, source)
: source || config.useDeprecatedSynchronousErrorHandling
? this._subscribe(subscriber)
: this._trySubscribe(subscriber)
);
return subscriber;
In this case src2$ has an operator, so it will call that. operator is defined as:
function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
}
where init depends on the operator that is used. Once again, here is map's init
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate( /* THIS IS `init()` */(source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
source is in fact src$. When source.subscribe() is called, it will end up calling the callback provided to new Observable(subscriber => { ... }). Calling subscriber.next(1) will call the (value: T) => { ... } from above, which will call subscriber.next(project.call(thisArg, value, index++));(project - the callback provided to map). Lastly, subscriber.next refers to console.log.
Coming back to Subject, this is what happens when the _subscribe method is called:
protected _subscribe(subscriber: Subscriber<T>): Subscription {
this._throwIfClosed(); // if unsubscribed
this._checkFinalizedStatuses(subscriber); // `error` or `complete` notifications
return this._innerSubscribe(subscriber);
}
protected _innerSubscribe(subscriber: Subscriber<any>) {
const { hasError, isStopped, observers } = this;
return hasError || isStopped
? EMPTY_SUBSCRIPTION
: (observers.push(subscriber), new Subscription(() => arrRemove(this.observers, subscriber)));
}
So, this is how Subject's list of subscribers are is populated. By returning new Subscription(() => arrRemove(this.observers, subscriber)), it ensures that then subscriber unsubscribes(due to complete/error notifications or simply subscriber.unsubscribe()), the inactive subscriber will be removed from the Subject's list.

What is the intention of the class `Model` in this code snippets?

I am reading a code base written by someone else. a Model class is defined in the following snippet, but I can not figure out why the writer defined this class. What is the intention to define this class other that using plain Object? What is the pattern here? Thank you very much!
const models = {}
export default class Model {
constructor() {
this._type = this.constructor.name
if (!models[this._type]) {
models[this._type] = this.constructor.prototype
}
}
}
const model = object => {
if (!object) {
return object
}
if (object._type && object._type != object.constructor.name) {
object.__proto__ = models[object._type]
}
(typeof object == "object") && Object.values(object).forEach(v => {
model(v)
})
return object
}
Its usage:
class xxxModel extends Model { ... }.
updated
and there are many occurrences of the following usage pattern:
const refreshData = function (...args) {
// do something to update UI.
const data = args[0]
Object.keys(data).forEach(key => model(this.data[key]))
}

how do I make this class reuseable in typescript? meta programming? subclasses? injected module?

I've got this EventsStorage typescript class that is responsible for storing and retrieving Event objects in ionic-storage (wrapper for sqlite and indexedDB). It uses my Event class throughout.
I would like to reuse a lot of this logic for something other than an Event, like a Widget.
I come from a ruby background where it would be relatively simple to extract all the storage logic, set a ruby var that is literally the class Event and use that var wherever I use Event. Can I do something similar in typescript? Is there another mechanic I can use to reuse the bulk of this class for something else, like Widget?
Ideally, my EventsStorage class becomes really lightweight, and I'm not just wrapping calls to this.some_storage_module.get_ids() or this.some_storage_module.insert_new_objs() -- which would have to be copy/pasted to every other instance I needed this.
Something like this:
export class EventsStorage { // extends BaseStorage (maybe??)
constructor(){
super(Event, 'events'); // or some small set of magical args
}
}
Here's the existing class:
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { Event } from '../classes/event';
// EventsStorage < EntityStorage
// - tracks local storage info
// - a key to an array of saved objects
// - a query() method that returns saved objects
#Injectable()
export class EventsStorage {
base_key: string;
ids_key: string;
constructor(
private storage: Storage
){
this.base_key = 'event';
this.ids_key = [this.base_key, 'ids'].join('_');
}
get_ids(): Promise<any>{
return this.storage.ready().then(() => {
return this.storage.get(this.ids_key).then((val) => {
if(val === null){
return [];
} else {
return val;
}
});
});
}
insert_new_objs(new_objs: any): Promise<any>{
return new_objs.reduce((prev: Promise<string>, cur: any): Promise<any> => {
return prev.then(() => {
return this.storage.set(cur._id, cur.event);
});
}, Promise.resolve()).then(() => {
console.log('saving event_ids');
return this.storage.set(this.ids_key, new_objs.map(obj => obj._id));
});
}
update(events: Event[]): Promise<any> {
let new_objs = events.map((event) => {
return {
_id: [this.base_key, event.id].join('_'),
event: event
};
});
return this.insert_new_objs(new_objs);
}
query(): Promise<Event[]>{
let events = [];
return this.get_ids().then((ids) => {
return ids.reduce((prev: Promise<string>, cur: string): Promise<any> => {
return prev.then(() => {
return this.get_id(cur).then((raw_event) => {
events = events.concat([raw_event as Event]);
return events;
});
});
}, Promise.resolve());
});
}
get_id(id: string): Promise<Event>{
return this.storage.get(id).then((raw_event) => {
return raw_event;
});
}
}
It looks to me like you want to use generics. You basically define some basic interface between all the things you'll want to store, and your code should depend on that interface. In your code as far as I can tell you only use the id property.
So it would look kinda like this
import { Event } from '...';
import { Widget } from '...';
interface HasId{
id: string;
}
class ItemsStorage<T extends HasId> {
....
get_id(id: string): Promise<T>{
...
}
}
const EventStorage = new ItemsStorage<Events>(storage);
const WidgetStorage = new ItemsStorage<Widget>(storage);
const ev = EventStorage.get_id('abc'); //type is Promise<Event>
const wd = WidgetStorage.get_id('def'); //type is Promise<Widget>
You can read more about generics here.
Edit:
1 - about subclassing - It's usually less preferable. If your ItemsStorage class need different behavior when dealing with Events vs Widgets, than subclassing is your solution. But if you have the same behavior for every class, one might call your code generic, and using generics is better.

Categories