WebdriverIO ES6 PageObject method is not a function error - javascript

I am attempting to define a PageObject using the syntax defined in the WebdriverIO Docs:
Parent Page
//page.js
export default class Page {
constructor() {
this.title = 'My Page';
}
open(path) {
browser.url(path);
}
}
Child PageObject
// login.page.js
import Page from './page';
class LoginPage extends Page {
open() {
super.open('/login');
}
}
export default new LoginPage();
Then when I call the open method of the Login Page:
const LoginPage = require('../../pages/login.page');
LoginPage.open();
I get a TypeError:
TypeError: LoginPage.open is not a function
[chrome #0-0] at World.module.exports ...
[chrome #0-0] at Promise (<anonymous>)
[chrome #0-0] at F (/.../node_modules/core-js/library/modules/_export.js:35:28)
Work around
I can work around this problem by re-writing my PageObjects using Object.create rather than the class keyword (as described in the above linked docs).
Its not essential that I use the class keyword but I don't like to not knowing why this is failing. Please forgive me if it is obvious why this isn't working I am very new to JS.

I think you are mixing ES6 and CommonJS syntax here. Change your test to use ES6 syntax and it should start working as you expect.
import LoginPage from '../../pages/login.page';
describe('login tests', () => {
beforeAll(() => {
LoginPage.open();
});
it('should do something', () => {
//do something
});
})

I found this to work for me
http://gelionprime.ml/2017/10/page-object-pattern-webdriverio/
PARENT
//page.js
class Page {
constructor() {
this.title = 'My Page';
}
open(path) {
browser.url(path);
}
}
module.exports = Page;
CHILD
// login.page.js
import Page from './page';
class LoginPage extends Page {
open() {
super.open('/login');
}
}
module.exports = new LoginPage();
test Spec file using page object
const LoginPage = require('./login.page.js');
describe('test', () => {
it('can open', () => {
LoginPage.open();
});
});

Related

TypeError: _API.default is not a constructor with Jest tests

I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));

How to call a method in another module JavaScript?

I have a class with method performing login
LoginPage.js
class loginPage {
fillCredentials(username, password) {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
return this;
}
clickLogin() {
cy.contains("Login").click();
}
}
export default loginPage;
I have another spec file for testing:
login.spec.js
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
});
However, it is giving an error of
(0 , _loginPage.fillCredentials) is not a function
I know its a wrong way of calling a method. Is there any way I can use the methods without creating an instance of class to access methods
You can do so if you make the methods static
class loginPage {
static fillCredentials(username, password) {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
//return this; // you can't return "this" because there is no this for static methods
}
static clickLogin() {
cy.contains("Login").click();
}
}
export default loginPage;
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
});
With static methods you lose this which refers to the class instance, and therefore lose the ability to chain methods,
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password').clickLogin() // can't do this with static
});
});
As functions instead of a class, this is the pattern
// LoginPage.js
export const fillCredentials = (username, password) => {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
return this;
}
export const clickLogin = () => {
cy.contains("Login").click();
}
// login.spec (same as you have above)
import { fillCredentials, clickLogin } from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
})
Be wary of encapsulating test code in page objects, it can lead to over-complicated code.
For example, if you now want to test what happens if password is not entered, you can't use fillCredentials because you can't omit the password parameter. So do you add a fillCredentialsWithoutPassword function? Do you modify fillCredentials to test if password is undefined?
So, what's going on is that you are mixing up the module structure with is defined by Closures with the class-instance pattern.
For this scenario (which is class-instance) the functions are NOT part of the object itself, but of it's PROTOTYPE.
So in order to get that function you should access it's prototype.
//create a class (class syntax and this one is pretty much the same)
function xx (){}
//this is what class syntax makes to create a method for the class
xx.prototype.theFunctionIwant = function (){}
//create an instance
var example = new xx()
//this is how you can spy that function in the test
xx.prototype.theFunctionIwant
//ƒ (){}
Try it out : )
The other answers given will work and solve your exact question, but I fear that making the functions static is an anti-pattern for the Page Object Model. Check out this article on how to set up a POM for Cypress. I would highly encourage you to instantiate the class, similar to below.
//LoginPage.js
export class LoginPage { ... }
//login.spec.js
import { LoginPage } from '../../support/PageObjects/loginPage';
describe('User Onboarding Emails', () => {
it('Verification email', () => {
const loginPage = new LoginPage()
cy.visit('/')
loginPage.fillCredentials('username','password')
loginPage.clickLogin()
});
});
You could also use a beforeEach() block to instantiate the variable before each test.
describe('User Onboarding Emails', () => {
let loginPage: LoginPage;
beforeEach(() => {
loginPage = new LoginPage();
});
it('Verification email', () => {
...
});
})
As an aside, it is usually preferred to name classes beginning with an uppercase (LoginPage vs. loginPage). When naming a class this way, you can easily differentiate the class vs. the instantiated variable.

Error "str.replace is not a function" when using store.queryRecord with Ember Octane

I'm following an Embercasts course (Ember + Rails). For the screencasts, they used Ember 3.0, but I'm using Octane.
In one video, a custom service is implemented. This is what my version looks like:
import Service, { inject as service } from '#ember/service';
export default class CurrentUserService extends Service {
#service store;
load() {
this.store.queryRecord('user', { me: true })
.then((user) => {
this.set('user', user);
})
.catch((e) => {
debugger;
});
}
}
In the load function, which is being called from a route, this.store.queryRecord() causes an error:
TypeError: str.replace is not a function
at Cache.func (index.js:64)
at Cache.get (index.js:774)
at decamelize (index.js:167)
at Cache.func (index.js:32)
at Cache.get (index.js:774)
at Object.dasherize (index.js:190)
at UserAdapter.pathForType (json-api.js:221)
at UserAdapter._buildURL (-private.js:293)
at UserAdapter.buildURL (-private.js:275)
at UserAdapter.urlForQueryRecord (user.js:13)
The relevant line is
var DECAMELIZE_CACHE = new _utils.Cache(1000, str => str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase());
This is the UserAdapter:
import ApplicationAdapter from './application';
export default class UserAdapter extends ApplicationAdapter {
urlForQueryRecord(query) {
if (query.me) {
delete query.me;
return `${super.buildURL(...arguments)}/me`;
}
return `${super.buildURL(...arguments)}`;
}
}
What's wrong here?
when you do super.buildURL(...arguments) you essentially do super.buildURL(query). And query is an object ({ me: true }) not a string while buildURL expects the modelName as first parameter.
So probably you wanna do something like this instead:
urlForQueryRecord(query, modelName) {
if (query.me) {
delete query.me;
return `${super.buildURL(modelName)}/me`;
}
return `${super.buildURL(modelName)}`;
}

test class method that return another class

I want to test whether the main class method of getService return the correct new class based on the correct conditional
// main.js
import ServiceA from './serviceA'
import ServiceB from './serviceB'
class Main {
constructor(){}
getService(serviceName){
switch(serviceName){
case 'serviceA':
return new ServiceA()
case 'serviceB':
return new ServiceB()
default
return null
}
}
}
Would it be possible to test that the returned class is correct? I tried something like this
import Main from './Main'
describe('Main method', () => {
describe('getService given ServiceA', () => {
it.skip('should return an instantiate of ServiceA class', function () {
const main = new Main();
const getService = spy(main, 'getService');
main.getService('serviceA');
expect(getService).to.be.an.instanceOf(ServiceA);
});
});
There shouldn't be a need to spy on getService(). Since you are just testing the input and output of the getService() without any dependencies. Spying would allow to see call count and arguments passed to the spy to see if it was called inside the method you are testing. How you had it is mostly correct as it is.
import Main from './Main'
describe('Main method', () => {
describe('getService given ServiceA', () => {
it('should return an instantiate of ServiceA class', function () {
const main = new Main();
const result = main.getService('serviceA');
expect(result).to.be.an.instanceOf(ServiceA);
});
});

testing a service call in jasmine

I am trying to write a unit-test for a function that calls a service. But am running into the error: TypeError: undefined is not a constructor
What I am trying to test is a service call that, on success, sets the value of the variable 'cards'.
I've created the appropriate mock for the service (CardService), which you can see in the spec file below
test.component.spec.ts
class MockCardService extends CardService {
constructor() {
super(null); // null is the http in service's constructor
}
getCardDetails(): any {
return Observable.of([{ 0: 'card1' }, { 1: 'card2' }]);
}
}
describe('MyComponent', () => {
let component: MyComponent;
let mockCardService: MockCardService;
beforeEach(() => {
mockCardService = new MockCardService();
component = new MyComponent(
mockCardService // add the mock service that runs before each test
);
});
// The failing test :(
it('should set the card variable to the value returned by the service', () => {
spyOn(mockCardService, 'getCardDetails').and.callThrough();
// Act
component.ngOnInit();
component.updateCards(); // call the function I am testing
// Assert
expect(component.cards).toConTainText('Card1');
});
And the component file with the function I'm testing:
export class MyComponent implements OnInit {
public cards: CardModel[] = [];
constructor(
private cardService: CardService,
) {
}
ngOnInit() {
this.updateCards(); // call the update card service
}
updateCards(): void {
this.cardService.getCardDetails().subscribe(
(cardsDetails) => {
this.cards = cardsDetails;
},
(err) => {
// todo: error handling
console.log(err);
}
);
}
}
Whenever this test runs I recieve the error:
TypeError: undefined is not a constructor (evaluating 'Observable_1.Observable.of([{ 0: 'card1' }, { 1: 'card2' }])') (line 22)
getCardDetails
updateCards
ngOnInit
And I can't figure out what I'm doing wrong, why 'getCardDetals.subscribe' is undefined. The MockCardService class I provided doesn't appear to be working for some reason.
(note that this.cardService.getCardDetails() is defined, if I log it out in the component itself )
Any help would be very much appreciated.
Author here:
I'm still not sure what is going wrong. But I was able to fix this by changing class MockCardService extends CardService { to just let MockCardService, and using that variable throughout. Good luck to anyone who runs into this!
MockCardService.getCardDetails() should return an Observable, so you can run subscribe in the component.

Categories

Resources