I am using protractor to test an Angular JS application.
I would like to use page object pattern therefor I need to export a function into another module.
Here is my page-login.js file:
require("./login.spec.js");
var LoginPage = function ()
{
this.userName = element(by.id('login_form_user_input'));
this.password = element(by.id('login_form_password_input')) ;
this.loginButton = element(by.id('login_form_signin_button'));
this.loginText = element(by.css('#mainGlobalSearchBtn'));
this.loginError = element(by.xpath('html/body/div[1]/div[1]/div[1]/form/div/p'));
this.login = function (userName, password)
{
LoginPage.userName.sendKeys(userName);
LoginPage.password.sendKeys(password);
LoginPage.loginButton.click ();
browser.waitForAngular ();
};
};
exports.LoginPage = LoginPage;
Now in another js file called login.spec.js under the same folder I would like to call the login this method :
var util = require ('util');
require("./myconfig.js");
describe('Login', function() {
var loginPage = require("./login-page.js");
var ptor;
beforeEach (function ()
{
ptor = protractor.getInstance();
ptor.get(settings.winURL);
waits(2000);
//Delete cookies to avoid saving password or username
});
it('should not login : incorrect login details', function()
{
loginPage.login('incorrectusername','incorrectpassword');
expect(loginPage.loginError.getText()).toContain('Access denied');
});
When Launching this code protractor is not entering username and passwoed in corresponding fields and I got this console ouput :
1) Login should not login : incorrect login details
Message:
TypeError: Object #<Object> has no method 'login'
Stacktrace:
TypeError: Object #<Object> has no method 'login'
at null.<anonymous> (C:\******\login.spec.js:34:20)
here is line 34 that is throwing the error :
loginPage.login('incorrectusername','incorrectpassword');
How can I use the login functioninside the login page function in another module?
The require( './login-page.js' ) returns a "class" function. It means that, to call login method of loginPage module you might to use new. Ex.:
it('should not login : incorrect login details', function()
{
var page = new loginPage();
page.login('incorrectusername','incorrectpassword');
expect(page.loginError.getText()).toContain('Access denied');
});
Remove the require("./login.spec.js"); of the begin of your module. The test is running before the module creation.
Protractor has this awesome option called onPrepare. You put this in your config.js file. Your onPrepare can reference a file.
In this file you can have all the variables and functions you need!
Instead of "this" you use global.
global.userName = element(by.id('login_form_user_input'));
global.password = element(by.id('login_form_password_input')) ;
global.loginButton = element(by.id('login_form_signin_button'));
global.loginText = element(by.css('#mainGlobalSearchBtn'));
global.loginError = element(by.xpath('html/body/div[1]/div[1]/div[1]/form/div/p'));
global.login = function (user, pw)
{
userName.sendKeys(user);
password.sendKeys(pw);
loginButton.click ();
browser.waitForAngular ();
};
then in your config you do:
//other config settings,
onPrepare: myvariables.js
in your spec, you don't need to require anything!
Just call what you need (minus the global)
beforeEach (function () {
ptor = protractor.getInstance();
ptor.get(settings.winURL);
waits(2000);
//Delete cookies to avoid saving password or username
});
it('should not login : incorrect login details', function()
{
login('incorrectusername','incorrectpassword');
expect(loginError.getText()).toContain('Access denied');
});
you also might want to name your userName variable and the userName arg and password different things. (fixed above)
Related
I'm trying to use fixtures to hold data for different tests, specifically user credentials. This is an example of the code. I'm getting 'Cannot read properties of undefined (reading 'data')'. I tried to google search , I found Cypress fixtures - Cannot read properties of undefined (reading 'data')
I used closure variable technique as reccomended in that post , yet I got reference error of unable to reference data.Please help me.I know cypress.config can be used but I want to keep that for global configs
Json(credentials.json):
{
"username":"*****",
"password":"*****"
}
Code:
import { LoginPage } from "./pageobject/login_page"
describe('Test Scenario', () => {
before(function () {
cy
.fixture('credentials').then(function (data) {
this.data = data
})
})
it('Simple login', () => {
cy.visit(Cypress.env('url'))
var loginpage = new LoginPage()
loginpage.EnterUsername(this.data.username)
loginpage.clickonSubmit()
loginpage.EnterPassword(this.data.password)
loginpage.clickonSubmit()
Cypress
.on('uncaught:exception', (err, runnable) => {
return false;
});
cy.
wait(10000)
cy.
get('span[id="user"]').should('have.text', this.data.username , 'User Login Unsuccessfully')
});
});
There's a few things need adjusting
use function () {} syntax in the it() block
use beforeEach() and alias to load the fixture, because data on this can be cleared (especially after login)
move uncaught:exception catcher to the top of the block
don't cy.wait(), instead add timeout to next command
.should() only has two parameters in this case, so use .and() to test the 2nd text
import { LoginPage } from './pageobject/login_page';
describe('Test Scenario', () => {
beforeEach(function () {
cy.fixture('credentials').as('data')
})
it('Simple login', function() {
Cypress.on('uncaught:exception', (err, runnable) => {
return false;
});
cy.visit(Cypress.env('url'));
var loginpage = new LoginPage();
loginpage.EnterUsername(this.data.username);
loginpage.clickonSubmit();
loginpage.EnterPassword(this.data.password);
loginpage.clickonSubmit();
cy.get('span[id="user"]', {timout:10_000})
.should('have.text', this.data.username)
.and('have.text', 'User Login Unsuccessfully')
})
})
I suspect it's because you are using an arrow function instead of a regular function, you cannot access the this object with an arrow function.
Cypress docs
If you store and access the fixture data using this test context
object, make sure to use function () { ... } callbacks. Otherwise the
test engine will NOT have this pointing at the test context.
change it to this:
it('Simple login', function() {
...
});
I am very new to Jasmine. I am intending to use it for with vanilla javascript project. The initial configuration was a breeze but I am receiving object not defined error while using spyOn.
I have downloaded the version 3.4.0 Jasmine Release Page and added the files 'as is' to my project. I then have changed jasmine.json file accordingly and see the all the example tests passing. However when try spyOn on a private object, I am getting undefined error,
if (typeof (DCA) == 'undefined') {
DCA = {
__namespace: true
};
}
DCA.Audit = {
//this function needs to be tested
callAuditLogAction: function (parameters) {
//Get an error saying D365 is not defined
D365.API.ExecuteAction("bu_AuditReadAccess", parameters,
function (result) { },
function (error) {
if (error != undefined && error.message != undefined) {
D365.Utility.alertDialog('An error occurred while trying to execute the Action. The response from server is:\n' + error.message);
}
}
);
}
}
and my spec class
describe('Audit', function(){
var audit;
beforeEach(function(){
audit = DCA.Audit;
})
describe('When calling Audit log function', function(){
beforeEach(function(){
})
it('Should call Execute Action', function(){
var D365 = {
API : {
ExecuteAction : function(){
console.log('called');
}
}
}
// expectation is console log with say hello
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
})
})
As you can see my spec class does not know about actual D365 object. I was hoping to stub the D365 object without having to inject it. Do I need to stub out whole 365 library and link it to my test runner html?
I got it working after some pondering. So the library containing D365 should still need to be added to my test runner html file. after that I can fake the method call like below,
it('Should call Execute Action', function(){
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
it is now working.
I have a page pages/login.js looks like:
function fillAndSubmitLogin(email, password) {
return this
.waitForElementVisible('#emailInput')
.setValue('#emailInput', email)
.setValue('#passwordInput', password)
.waitForElementVisible('#loginSubmitButton')
.click('#loginSubmitButton');
}
export default {
commands: [
fillAndSubmitLogin
],
elements: {
emailInput: 'input#email',
passwordInput: 'input[type=password]',
TFAInput: 'input#token',
loginSubmitButton: '.form-actions button.btn.btn-danger'
}
};
I have another page pages/hompage.js homepage.js attempts to include pages/login.js as a section
import login from "./login.js";
module.exports = {
url: 'http://localhost:2001',
sections: {
login: {
selector: 'div.login-wrapper',
...login
}
}
};
I then have a test case that attempts to login on the hompage section
'Homepage Users can login': (client) => {
const homepage = client.page.homepage();
homepage
.navigate()
.expect.section('#login').to.be.visible;
const login = homepage.section.login;
login
.fillAndSubmitLogin('user#test.com', 'password');
client.end();
}
This test then fails with the following error
TypeError: login.fillAndSubmitLogin is not a function
at Object.Homepage Users can login (/Users/kevzettler//frontend/test/nightwatch/specs/homepage.spec.js:32:6)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
login.fillAndSubmitLogin is not a function
at Object.Homepage Users can login (/Users/kevzettler//frontend/test/nightwatch/specs/homepage.spec.js:32:6)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
According to the Nightwatch docs, any commands that are exported in page objects should be plain JavaScript objects with a key being a command name and the value being a function. For example:
var googleCommands = {
submit: function() {
this.api.pause(1000);
return this.waitForElementVisible('#submitButton', 1000)
.click('#submitButton')
.waitForElementNotPresent('#submitButton');
}
};
module.exports = {
commands: [googleCommands],
elements: //...etc ...
// etc...
}
In this example, the module exports googleCommands, which is a command object which has a key (submit) and a corresponding function. I believe you should refactor your code as follows:
function fillAndSubmitLogin = {
fillAndSubmitLogin: function(email, password) {
return this
.waitForElementVisible('#emailInput')
.setValue('#emailInput', email)
.setValue('#passwordInput', password)
.waitForElementVisible('#loginSubmitButton')
.click('#loginSubmitButton');
}
};
Of course, you don't have to make the command name the same in both places (as the example shows (googleCommands/submit). This allows you to expose a variety of functions in one command. Hope that answers the question!
I have built a small prototype project using CloudKit JS and am now starting to build the next version of it and am wanting to use Ember as I have some basic experience with it. However, I am not too sure where to place the CloudKit JS code. For example where should I add the configure part and the auth function? I think that once I find the spot for the auth code, I could then add some of my query functions into the individual views and components, right?
Here is my configure code (with the container and id removed):
CloudKit.configure({
containers: [{
containerIdentifier: '###',
// #todo Must generate a production token for app store version
apiToken: '###',
auth: {
persist: true
},
// #todo Must switch to production for app store version
environment: 'development'
}]
});
Here is the auth function:
function setupAuth() {
// Get the container.
var container = CloudKit.getDefaultContainer();
//Function to call when user logs in
function gotoAuthenticatedState( userInfo ) {
// Checks if user allows us to look up name
var userName = '';
if ( userInfo.isDiscoverable ) {
userName = userInfo.firstName + ' ' + userInfo.lastName;
} else {
userName = 'User record name: ' + userInfo.userRecordName;
}
//Calls out initialization function
init();
//Sets up UI for logged in users
setAuthenticatedUI( userName );
//Register logged out function
container
.whenUserSignsOut()
.then( gotoUnauthenticatedState );
}
//Function to call when user logs out
function gotoUnauthenticatedState( error ) {
//Checks if error occurred
if ( error && error.ckErrorCode === 'AUTH_PERSIST_ERROR' ) {
displayError( logOutError, 'Error code: AUTH_PERSIST_ERROR' );
}
// Sets up the UI for logged out users
setUnauthenticatedUI();
//Register logged in function
container
.whenUserSignsIn()
.then( gotoAuthenticatedState )
.catch( gotoUnauthenticatedState );
}
// Check a user is signed in and render the appropriate button.
return container.setUpAuth()
.then( function( userInfo ) {
// userInfo is the signed-in user or null.
if ( userInfo ) {
gotoAuthenticatedState( userInfo );
} else {
gotoUnauthenticatedState();
}
});
}
The init() then calls functions to setup the queries to adds a chart to the page using records. The setAuthenticatedUI() and setUnauthenticatedUI() functions simply apply and remove classes once the user has been authenticated.
The answer pretty much depends on the version of Ember you're using and if how you are planning on using it. With routes? Simple routes? RouteHandlers?
For example, if you are at Ember v2.3.0, you could consider using dependency injection (https://guides.emberjs.com/v2.3.0/applications/dependency-injection/) to provide a configured container instance to the rest of your app, e.g.:
export function initialize(application) {
var container = CloudKit.configure(config).getDefaultContainer();
application.register('ckcontainer:main', container);
application.inject('route', 'ckcontainer', 'ckcontainer:main');
}
export default {
name: 'ckcontainer',
initialize: initialize
};
Then in a route, you can obtain a reference like so:
export default Ember.Route.extend({
activate() {
// The ckcontainer property is injected into all routes
var db = this.get('ckcontainer').privateCloudDatabase;
}
});
-HTH
I am trying to move my protractor code to a page object design pattern I started by my login tests and please find my code below.
When running my test, protractor load the page bu fails when trying to enter text into the username password inputs, I have tried to locate username text area using by.id and by.input but both did not work.
Please also note that when I run my login test before using the page object pattern protractor is able to find the text area.
page-login.js :
var loginPage = function ()
{
this.userName = element(by.input('userName'));
this.password = element(by.input('userPassword')) ;
this.loginButton = element(by.id('login_form_signin_button'));
this.loginText = element(by.css('#mainGlobalSearchBtn'));
this.loginError = element(by.xpath('html/body/div[1]/div[1]/div[1]/form/div/p'));
this.login = function (userName, password)
{
loginPage.userName.sendKeys(userName);
loginPage.password.sendKeys(password);
loginPage.loginButton.click ();
browser.waitForAngular ();
}
};
it('should not login : incorrect login details', function()
{
var loginPage = new loginPage();
loginPage.login('incorrectusername','incorrectpassword');
expect(loginPage.loginError.getText()).toContain('Access denied');
});
Console output :
1) Login should not login : incorrect login details
Message:
TypeError: undefined is not a function
Stacktrace:
TypeError: undefined is not a function
at null.<anonymous> (C:\Users\orsyp\DUX\k_workload_ar\ui\e2e\login.spec.js:3
1:26)
at C:\Users\orsyp\DUX\k_workload_ar\ui\node_modules\grunt-protractor-runner\
node_modules\protractor\jasminewd\index.js:54:12
at webdriver.promise.ControlFlow.runInNewFrame_ (C:\Users\orsyp\DUX\k_worklo
ad_ar\ui\node_modules\grunt-protractor-runner\node_modules\protractor\node_modul
es\selenium-webdriver\lib\webdriver\promise.js:1445:20)
at webdriver.promise.ControlFlow.runEventLoop_ (C:\Users\orsyp\DUX\k_workloa
d_ar\ui\node_modules\grunt-protractor-runner\node_modules\protractor\node_module
s\selenium-webdriver\lib\webdriver\promise.js:1310:8)
at wrapper [as _onTimeout] (timers.js:252:14)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
If I follow the Getting started doc example, you should add var loginPage = new loginPage(); into the it function.
it('should not login : incorrect login details', function() {
//add this line
var loginPage = new loginPage();
loginPage.login('incorrectusername','incorrectpassword');
expect(loginPage.loginError.getText()).toContain('Access denied');
});
const log = Factory.getLogger("Page.DashDashboardPage");
export class DashDashboardPage extends PageBase{
private lblDashPageTitle : any;
private lnkDashMyDashboard : any;
private btnDashNewTeamDashboard : any;
private txtDashSearchRecord : any;
constructor(){
super();
const element = Elements.DashDashboardPage;
this.lblDashPageTitle = super.findLocators(element.lblDashPageTitle.findBy,element.lblDashPageTitle.value);
this.lnkDashMyDashboard = super.findLocators(element.lnkDashMyDashboard.findBy,element.lnkDashMyDashboard.value);
this.btnDashNewTeamDashboard= super.findLocators(element.btnDashNewTeamDashboard.findBy,element.btnDashNewTeamDashboard.value);
this.txtDashSearchRecord = super.findLocators(element.txtDashSearchRecord.findBy,element.txtDashSearchRecord.value);
}
/**
* Get: load dash-dashboard base url
* #returns {DashDashboardPage}
*/
public get(): DashDashboardPage{
ConfigRoute.visit_page('http://op.xxx-tek.com/test/');
log.info("Step: navigate to http://op.xxx-tek.com/test/ [:get:]");
return new DashDashboardPage();
}
/**
* Validate: verify dash-board page title
* #param title
* #returns {DashDashboardPage}
*/
public check_And_Validate_dash_page_title(title: string): DashDashboardPage{
this.Helper_Assertion.expectToEqual(this.lblDashPageTitle,title);
log.info("Validate: Verify dash page tile [:check_And_Validate_dash_page_title:]");
return new DashDashboardPage();
}
}