Passing text from HTML as variable using protractor - jasmine

I have a table with unknown values (which I need to log in later) and I would like to save one of it with class currentWeek to a variable. HTML is as following:
<tr _ngcontent-c21="" class="currentWeek">
<th _ngcontent-c21="" class="text-center">Value1Foo</th>
(...)
</tr>
In proctracor I created in Helper.ts:
static getfooOfTheWeek() {
let child = element(by.css('.currentWeek')).$('.text-center');
describe('Get foo', function () {
it('get foo', function () {
browser.driver.get('tablepage');
browser.sleep(3000);
return ((child).getText()).toString();
})
})
}
and in file maintest.ts:
describe('Get FOO', function () {
var FOO=Helper.getfooOfTheWeek();
browser.sleep(2000);
//use the value set in Helper.getfooOfTheWeek();
NegativeTest.SomeTest(FOO);
});
but it fails ususally with - Failed: each key must be a number of string; got undefined - therefore I think the FOO is save as an Object, not as string.
I thought also using JSON (to use JSON.parse()), but developers can't gives the values to the table from JSON
Any path what I can try?

Got it - found this issue about saving as string instead of object: Protractor: element.getText() returns an object and not String where I need to resolve the promise
and instead of passing it as variable with var FOO=Helper.getfooOfTheWeek(); I saved it as browser.params.Foo
static getfooOfTheWeek() {
let child = element(by.css('.currentWeek')).$('.text-center');
browser.driver.get('tablepage');
browser.sleep(3000);
child.getText().then(function(text) {
browser.params.Foo=text;
});
}
and I can run NegativeTest.SomeTest(); where the variable is passed in SomeTest() as browser.params.Foo (instead of writting there FOO)

Related

Cypress get attribute value and assign it to variable in function

I am trying to get attribute value and return it from a function.
Here is the code that is working and can be used in a normal test class (into the integration folder).
describe('Example shows how to get attribute value.', () => {
// 'it' is used to create test case. You can add a name of the test case. You can have multiple test cases in one JS class.
it('Get attribute value.', () => {
// Cypress is not able to work with new tabs. It is not possible to switch between tabs. Cypress can manipulate the DOM tree, so we can change the element attributes and open the hyperlink in the same browser tab.
// 'visit()' method is used for navigating to URL address.
cy.visit('https://demoqa.com/links')
cy.xpath('//*[#id="simpleLink"]').then(function (element) {
// 'prop()' method is used to get the attribute value.
const url = element.prop('href')
cy.visit(url)
})
// Assert URL.
cy.url().should('include', 'demoqa.com').should('eq', 'https://demoqa.com/');
})
If I use the code that way - everything is working as expected.
But if I want to re-use the code and create a function like this:
// Give a value of the variable to use it for next function.
functionName = 'addAttribute';
// Declare a Cypress child custom command.
Cypress.Commands.add(functionName, { prevSubject: 'element' }, (subject: any, attributeName: string, attributeValue: string) => {
// Create a try-catch statement. If the function fails - we will recieve the error message.
try {
// Create the function steps after this comment.
cy
.wrap(subject)
.invoke('attr', attributeName, attributeValue)
.should('have.attr', attributeName, attributeValue)
} catch (error) {
// Create the error log and show it to the UI. Show the function name, the class where the function is located and catched error.
let errorMessage = `----------ERROR! It seems that we have an error. Please review the "${functionName}" function from "${__filename.split(__dirname + "/").pop()}" . The error is: ${error}`;
cy.log(errorMessage);
console.log(errorMessage);
}
})
The result is 'object' and I am not sure how to process it.
Here is the rest of the code:
describe("'getAttribute' custom child command example.", () => {
it("example shows how to use 'getAttribute' custom child command.", () => {
cy.visit('https://demoqa.com/buttons');
let attributeValue = cy.element('xpath','(//*[contains(text(),"Click Me")])[3]').getAttribute('class');
cy.log(`The attribute values is: ${attributeValue}`)
});
});
You have to do as below to have a return value:
let attributeValue = '';
cy.element('xpath', '(//*[contains(text(),"Click Me")])[3]')
.getAttribute('class')
.then((attr) => {
attributeValue = attr;
});
cy.log('The attribute values is:' + attributeValue)

How to override 'data-testid' in the 'findByTestId function from Cypress Testing Library

Most of my existing codebase uses a 'id' only in few places 'data-testId' attribute present.
tried this code
import { configure } from '#testing-library/cypress';
configure({ testIdAttribute: ['data-testId','id'] });
But, still its not working.
Is there any way to use 'id' value in any of the testing-library functions.
My HTML code is something like:
<div class="some random class name" id="userprofile-open" role="button">SB</div>
I want click that element with this code:
cy.findByTestId("userprofile-open", { timeout: 120000 }).click();
I don't think you can configure testing-library with an array of ids, ref API configuration,
import { configure } from '#testing-library/cypress'
configure({ testIdAttribute: 'id' })
But even this fails. Instead you have to use the Cypress command to change the attribute name (only one name is allowed).
cy.configureCypressTestingLibrary({ testIdAttribute: 'id' })
To use either/or attribute name you can change the attribute name on the fly, wrapping it in a custom command (based on Custom Queries)
Cypress.Commands.add('findByTestIdOrId', (idToFind) => {
let result;
const { queryHelpers } = require('#testing-library/dom');
let queryAllByTestId = queryHelpers.queryAllByAttribute.bind(null, 'data-testId');
result = queryAllByTestId(Cypress.$('body')[0], idToFind)
if (result.length) return result;
queryAllByTestId = queryHelpers.queryAllByAttribute.bind(null, 'id');
result = queryAllByTestId(Cypress.$('body')[0], idToFind);
if (result.length) return result;
throw `Unable to find an element by: [data-test-id="${idToFind}"] or [id="${idToFind}"]`
})
cy.findByTestIdOrId('my-id')
.should('have.attr', 'id', 'my-id')
// passes and logs "expected <div#my-id> to have attribute id with the value my-id"
Note this custom command works only for synchronous DOM.
If you need to have Cypress retry and search for either/or attribute, don't use testing-library in the custom command.
Instead use Cypress .should() to enable retry
Cypress.Commands.add('findByTestIdOrId', (selector, idToFind) => {
cy.get(selector)
.should('satisfy', $els => {
const attrs = [...$els].reduce((acc, el) => {
const id = el.id || el.getAttribute('data-test-id') // either/or attribute
if (id) {
acc.push(id)
}
return acc
}, [])
return attrs.some(attr => attr === idToFind); // retries when false
})
.first(); // may be more than one
})
cy.findByTestIdOrId('div', 'my-id')
.should('have.attr', 'id', 'my-id')
// passes and logs "expected <div#my-id> to have attribute id with the value my-id"
The usual cypress way - which has an inherent check on the element visibility and existence as well as included retries for a period of time is using cy.get()
If you want to select element using property like data-id you need this sintax: cy.get('[propertyName="propertyValue"]')
If you want select an element by CSS selector you just pass CSS selector like this:
cy.get('#id')

Angular 2 component global variable is undefined?

Im trying to create an if statement within a template to display a certain block of text when an array length is < 1.
This is how I try to do it:
<div *ngIf="socialNetworks.length > 1">
<div class="alert-box warning radius">You can still connect some accounts</div>
<button class="btn btn-primary" *ngFor="let network of socialNetworks" (click)="loginSocialNetwork(network)">
{{network.title}}
</button>
</div>
But I always get an error saying that it cannot read property length of undefined.
I define the variable socialNetworks[] here in an Angular 2 component:
export class MyAccountComponent implements OnInit {
socialNetworks: socialNetwork[];
campaigns: Campaign[];
showGreeting: boolean = true;
constructor(
private _dataservice: DataService) {
}
Then, in a seperate method, I set the value from a response from a pyramid view here:
getSocialNetworks() {
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => this.socialNetworks = res,
err => this.logError(err)
)
}
Even if I add a console.log statement at the end here to see the value of this.socialNetworks, it says it is undefined. But in a debugger I can see that the value of this.socialNetworks is not undefined.
So my question is, am I just referencing the global variable incorrectly, or am I missing/misunderstanding something all together?
Looks to me like socialNetworks isn't set as an empty array on construction, so it will be undefined on init. Try to change the top socialNetwork to:
socialNetworks: socialNetwork[] = [];
The issue we were speaking about below in the comments is most likely to do with this within the subscribe method. It is assigning it to the incorrect scope.
try below:
getSocialNetworks() {
var _that = this;
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => _that.socialNetworks = res,
err => _that.logError(err)
)
}
If the api call uses some external library it might not be hooked into angular 2's digest cycle (Facebook SDK isn't for example). You can debug using Augury, or for simplicity set a global window property in your constructor to access your component:
constructor(private _dataservice: DataService) {
window['MAC'] = this;
}
Then you can check in the dev tools of your browser to see if the socialNetworks array is actually being set:
MAC.socialNetworks
You can always in your HTML use some quick code to show it as JSON to debug what is going on:
{{socialNetworks | json}}
If the page doesn't show it having a value but you see it in the console, then your _dataService isn't triggering change detection and you have to do it manually. You can use ApplicationRef by injecting it in your constructor and calling tick() on it after you set your data.
constructor(
private _dataservice: DataService,
private _appref: ApplicationRef ) {
}
getSocialNetworks() {
var _that = this;
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => {
_that.socialNetworks = res;
this._appref.tick(); // force change detection
}, err => _that.logError(err)
)
}
As for the calling length on undefined, since you're asynchronously pulling data your socialNetworks property is undefined to start. You can initialize it to an empty array like in the answer by #JacobS or modify your check to account for it:
<div *ngIf="socialNetworks && socialNetworks.length > 1">

How to assign a back end data value to a string variable in AngularJS?

I was trying to define one sort of global variable which value will be reflecting in 2/3 different templates (directives). For that I used angular factory as follows:
app.factory('MyService', function ($http) {
return {
firstNumber: function (){
//return selectedNumber = "200";
var selectedNumber = "";
var selectedNumber = $http.get("/count.do").success(function (data) {
console.log('First Number: ', data[0].count)
});
return selectedNumber;
}
};
});
As you can see 'selectedNumber' is that common variable. Problem is when I am hard coding the value as "200" and from controller calling as follows:
//Init Number
$scope.selectedNumber= MyService.firstNumber();
This whole process is working fine. But as soon as I am trying to get the value from back end (which you can see above) getting {} object.
I did some research on this and understanding that my concept on Angular object and String manipulation is not clear...can anyone please help me to understand the mistake I am doing and to resolve this situation.
Well, i got my expected outcome by using 'callback' service as follows:
In my factory i just called the '$http.get':
app.factory('MyService', function ($http) {
return {
firstNumber: function (){
$http.get("/count.do").success(callback);
}
};
});
And then from controller i received the data and assigned as follows:
//Init Number
MyService.firstNumber(function(data) {
$scope.selectedNumber = data[0].count;
});
I don't know whether it is a good solution or what, will really appreciate for any comment on this solution plz.
Thanks

How can I access a field of a Parse.com Object when I don't know the fieldname?

When I use the code below to access a class row for the current user?
Parse reports that the function returns a data set thus:
{"user":{"__type":"Pointer""className":"_User""objectId":"NFYHCP6Ftw"}
"known_fieldname":"Value"
"known_fn2":"value2"
"known_fn3":"value3"
"objectId":"obFbHtMW4E"
"createdAt":"2014-09-16T15:47:55.047Z"
"updatedAt":"2014-09-16T16:10:55.318Z"
"__type":"Object"
"className":"Answers"}
Parse CloudCode:
Parse.Cloud.define("Answers", function(request, response) {
var query = new Parse.Query("Answers");
query.equalTo("user", Parse.User.current());
query.first({
success: function(obj) {
// kick out "value"...
console.log("A field I know="+obj.get("known_fieldname"));
// this for loop doesn't loop thru the field "keys" and "values"
// what do I replace it with?
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log("Answer="+obj.get(key));
// add to a dictionary to return to caller
}
}
response.success(obj);
},
error: function() {
response.error("Failed to get any answers.");
}
});
});
To get a list of keys for an Object one needs to do this:
var keys = Object.keys(obj.toJSON());
One can then loop thru keys thus:
for (var key in keys) { ...
I think the Parse developers assumed one would know the names of fields within a Parse class and so didn't provide (yet) a clean API to access them.

Resources