how to get an outerHTML, innerHTML, and getText() of an element - javascript

I'm newbie to protractor framework, and I've been trying for a while to figure out how to get the outerHTML/InnerHTML/getText() (child elements) so that I can test if an element <img> is rendered onto a view. Heads up, we've an ng-grid and I'm trying to look up in its first column to see if it contains an img element also check if it contains an attribute i.e. src=res/someImg.png.
Here is what I got
html
<div>
<a>
<div>
<div>
<span>
<i><img src="res/someImg.png"></i>
</span>
</div>
<div>
...
</div>
<div>
...
</div>
</div>
</a>
</div>
test
it('should render an icon in agent list', function () {
var row = element.all(by.repeater('row in renderedRows')).get(3);
expect(row).not.toEqual(null); //pass
expect(row.element(by.css('img')).getAttribute('src').getText()).toMatch(/someImg.png/);//fail with null
expect(row.element(by.css('span')).outerHTML).toBe('<i><img src="res/someImg.png"></i>'); //fails
expect(row.element(by.css('i')).innerHTML).toBe('<img src="res/someImg.png">'); //fails
});
Can someone tell what am I doing wrong please?

Use getAttribute() in all 3 cases - for src, outerHTML and innerHTML:
expect(row.element(by.css('img')).getAttribute('src')).toMatch(/someImg.png/);
expect(row.element(by.css('span')).getAttribute('outerHTML')).toBe('<i><img src="res/someImg.png"></i>');
expect(row.element(by.css('i')).getAttribute('innerHTML')).toBe('<img src="res/someImg.png">');
Tested - works for me.

A little more explicitly:
expect(row.element(by.css('img')).getAttribute('src')).toMatch(/someImg.png/);
expect(row.element(by.css('span')).getOuterHtml()).toBe('<i><img src="res/someImg.png"></i>');
expect(row.element(by.css('i')).getInnerHtml()).toBe('<img src="res/someImg.png">');

As alecxe said on Aug 24 '16, "getOuterHtml() and getInnerHtml() are now deprecated in WebDriverJS and Protractor" (see comment from https://stackoverflow.com/a/27575804/3482730)
You should now use the following to get innerHTML code (as indicated here: https://github.com/angular/protractor/issues/4041#issuecomment-372022296):
let i = browser.executeScript("return arguments[0].innerHTML;", element(locator)) as Promise<string>;
Example using a helper function:
function getInnerHTML(elem: ElementFinder): Promise<string> {
return getInnerHTMLCommon(elem, elem.browser_);
}
function getInnerHTMLCommon(elem: WebElement|ElementFinder, webBrowser: ProtractorBrowser): Promise<string> {
return webBrowser.executeScript("return arguments[0].innerHTML;", elem) as Promise<string>;
}
const html = await getInnerHTML(browser.element(by.xpath("div[1]")));
console.log(html);

Related

How can I save buttons in variable, created by Element.insertAdjacentHTML() method?

as I said in title I have problem with HTML elements created with Element.insertAdjacentHTML() method, I'm trying about an hour to solve this but can't. I have button that create new HTML elements, couple of that elements is new buttons with same class or id, it's no matter, that I need to catch in some variable and than again use for event listener, for some reason the class or id for these new created button doesn't exist, is there any way to catch it and use it later, I need Vanila Javascript?
There is over 500 lines of code, this is only for mentioned method
btnClaim.addEventListener("click", () => {
rewardCurrent.style.display = "none";
claimedRewards.push(currentReward);
rewardsList.innerHTML = ``;
claimedRewards.forEach(function (rew, i) {
const html = `
<div class="reward" id="${i}">
<div class="img-text-cont">
<img src="${rew.imgUrl}" alt="">
<div class="text-cont">
<p class="claimed-reward-title">${rew.title}</p>
<p class="claimed-reward-price">$${rew.price}</p>
</div>
</div>
<div class="claimed-rewards-action">
<button id="btn-sell2">Sell</button>
<button id="btn-ship">Ship</button>
</div>
</div>
`;
rewardsList.insertAdjacentHTML("afterbegin", html);
I need that btn-sell2 and btn-ship buttons in variables.
your element is going to be created and doesn't exist at the time page loads, so js addeventlistener will throw an error. to solve you have 2 ways.
1- use parent node that element will be created inside.
addevenlistener to parent and use
parent.addeventlistener( event, function (event){
if(event.target.classList.contains("childClass") {}
}
2- give addeventlistener when creating the element :
function createElement () {
const elem = -craete elemnt-
elem.addeventlistener(event, function);
}

Cypress contains ignore elements

I want to find a clickable element using cypress. The clickable element always includes the text "Login" and is inside the container div. However, the catch is that I don't know if the clickable element is modeled using an <button>, <a>, or <input type="submit">.
My HTML sometimes look like this:
<nav>
<button>Wrong Login Button</button>
</nav>
<div class='container'>
...
<div>
<h2>Login</h2>
...
<button>Login</button>
</div>
</div>
And sometimes like this example:
<div class='container'>
...
<div>
<h2>Login</h2>
...
<div>
<div>
<a>Login</a>
</div>
</div>
</div>
</div>
It is sometimes a <button>, sometimes a <a> or <input type="submit">. And sometimes there are also more nested divs in there.
How can I find the clickable element (button, a or submit) element using cypress?
I only want to find the clickable element, so if there is a heading with the text "Login", it should be ignored.
This is nearly the answer:
cy.get('.container').contains('Login')
However, I somehow need to ignore all elements other than (button, a, and input[type=submit]). Because otherwise it would return the Login
I need something like:
cy.get('.container').find('button OR a OR input[type=submit]').contains('Login')
I thought of writing a custom command that can be used as the example below:
cy.get('.container').findClickable('Login')
The implementation could look similar as the example below:
Cypress.Commands.add('findClickable', {prevSubject: ['element']}, (
subject: Cypress.Chainable<HTMLElement>,
text: string,
options?: Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>,
) => {
const buttons = cy.wrap(subject).find('button').contains(text)
const links = cy.wrap(subject).find('a').contains(text)
const submits = cy.wrap(subject).find('input[type=submit]').contains(text)
if (buttons.length + links.length + submits.length !== 1) {
throw new DOMException(
`Didn't find exactly one element. Found ${buttons.length} buttons, ${links.length} links, and ${submits.length} submits.`)
}
if (buttons.length === 1)
return buttons[0]
else if (links.length === 1)
return links[0]
else
return submits[0]
})
I know that the code above does not work because of many reasons. First, there is no .length attribute and second, cypress would fail because of the timeout of the first find('buttons') method.
Does anyone know how to implement the custom command the right way?
You can use the within and contains command with a combination of selector and text.
cy.get('.container').within(() => {
cy.contains('button', 'Login').click()
cy.get('input[type=submit]').click()
cy.contains('Login').click()
})
For Multiple Login, First we are checking the button is enabled or disabled, then we are clicking on it.
cy.contains('Login').each(($ele) => {
if($ele.is(":enabled")){
cy.wrap($ele).click()
}
})
You can also do, this is a more optimized code for the above snippet.
cy.contains('Login').filter(':enabled').click()
It is possible to search for multiple selectors when separating them with a comma:
cy.get('button, a, input[type=submit]')
So, search for all clickable elements and then filter for the clickable elements containing the required text.
Cypress.Commands.add('findClickable', {prevSubject: ['element']}, (
subject: Cypress.Chainable<HTMLElement>,
text: string,
_options?: Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>,
) =>
cy.wrap(subject)
.find('button, a, input[type=submit]')
.filter(':contains(' + text + ')'),
)
Usage:
cy.get('.container').findClickable('Login')

Avoid repetition on HTML element, React

I have the following (simplified) HTML in my React project:
const { title } = this.props;
if(title) {
return (
<div class="someClass" onClick={someFunction} title={title}>
//Whatever..
</div>
)
}
else {
return (
<div class="someClass" onClick={someFunction}>
//Whatever..
</div>
)
}
Note that the if and else return values are identical except for the title attribute.
My question is, how can I prevent this repetition, but only add title when required.
I know I can add title to every element and just leave it blank if not required, but I do not want title attributes all over my HTML unless they are actually doing something.
I have tried:
var element = (
<div class="someClass" onClick={someFunction} title={title}>
//Whatever..
</div>
)
if(title)
element.title = //Whatever
But this did not work.
I have also tried
element.setAttribute('title', title);
But this did not work either.
Both the attempts above crashed the page.
Any ideas? Surely it can be done....
you can use:
title = {title?title:null}
if there is title in your props this will add the attribute title with the specified value to the element, otherwise no title attribute will be added.
In the React world with JSX you should use className instead of class.HTML doesn't exist in React the syntax that look like HTML is called JSX which just a syntactic sugar.
When you code hits a return statement everything that comes after will never be executed.So instead of doing:
if(condition){
return ...
}
else{
return ...
}
do can do:
if(condition){
return (<div>...</div>);
}
return (<div>...</div>)
So you don't need the else
Stop doing manual DOM manipulation like in Vanilla Javascriptby doing element.setAttribute('title', title); you don't need that in React.Please take your time to learn React basics.

get two divs by class name in karma test (Angular 4.0)

I have something like this in view:
<div>
<div class="header-title">Example title 1</div>
</div>
<div>
<div class="header-title">Example title 2</div>
</div>
In my karma test I would like to investigate all divs by class name and check if inner text is correct so I have following code in test:
[...]
debugTest = fixture.debugElement.query(By.css('.header-title'));
elementTest = debugTest.nativeElement;
[...]
it('should component div has a correct value', () => {
fixture.detectChanges();
const content = elementTest.textContent;
expect(content).toContain('Example Title 1');
});
Following code works but I always get the first dom with .header-title class. How to extract next one? What if I have 20 divs with the same class name how to test them all?
Use queryAll() instead of query(), which returns an array.
query() returns single DebugElement which is always the first matching element, whereas queryAll() returns DebugElement[].
debugTest = fixture.debugElement.queryAll(By.css('.header-title'));
So that you can access like
elementTest1 = debugTest[0].nativeElement;
elementTest2 = debugTest[1].nativeElement;

Setting HTML Button`onclick` in template literal

I have an html template that i'm using template literals for. The function looks like the below
// postCreator.js
export const blogPostMaker = ({ title, content, address, id }, deletePost) => {
const innerHtml = `
<blog-post>
<h1 class='title'>${title}</h1>
<p class='content'>${content}</p>
<p class='address'>${address}</p>
<button onclick='${() => deletePost(id)}'>Delete</button>
</blog-post>
`
return innerHtml
}
//Blog.js
postHelper.getSeriesOfPosts(10)
.then(resp => {
resp.forEach(post => (blogDiv.innerHTML += blogPostMaker(post, postHelper.deletePost)))
})
What I can't figure out is how to get the onclick to work. I've tried passing in an anon function in Blog.js to the postCreator as well with no luck.
Any ideas?
If you don't want to expose the event callback globally, you should attach it in the JS part of the code with addEventListener() :
// postCreator.js
export const blogPostMaker = ({ title, content, address, id }) =>
`
<blog-post id='${id}'>
<h1 class='title'>${title}</h1>
<p class='content'>${content}</p>
<p class='address'>${address}</p>
<button>Delete</button>
</blog-post>
`
//Blog.js
postHelper.getSeriesOfPosts(10).then(resp => {
resp.forEach(post => {
blogDiv.innerHTML += blogPostMaker(post)
blogDiv.querySelector(`blog-post[id="${post.id}"] > button`)
.addEventListener('click', () => postHelper.deletePost(post.id))
})
Note: it's not the most efficient way to do it but it keeps your file structure.
Instead I would create the <button> directly with createElement() and then add it to DOM with appendChild(), or I would use a DocumentFragment or a <template> in order to avoid querySelector() on all of the blogDiv.
If you absolutely want to use inline JS without exposing the helper you'll need to define your as a component (for example a Custom Element).
Miroslav Savovski's solution works but they did not explain why, so I thought I would add this answer with the reasoning behind that and a step-by-step of how it is actually working, and why the OP's solution was not working initially.
TLDR? Scroll to the last two code snippets.
With template literals when you put a function inside of them it executes that function, so let's say we have a simple function like this that just returns a string of 'blue':
const getBlueColor = () => 'blue'
And then we call this function inside of a template literal like this:
<div>${getBlueColor()}</div>
What happens is that the getBlueColor() is called right when that code is executed.
Now lets say we wanted to do this onclick instead like this:
<div onclick="${getBlueColor()}"></div>
What is happening here is that getBlueColor is not executed onclick, it's actually executed whenever this template literal is executed.
The way we fix this is to prevent the template literal from executing this function by simply removing the template literal:
<div onclick="getBlueColor()"></div>
But now let's say you want to pass in some parameters to a function like getOppositeColor(color) like this:
<div onclick="getOppositeColor(color)"><div>
This will not work because color won't be defined. So you need to wrap that with a template literal and in a string like this:
<div onclick="getOppositeColor('${color}')"><div>
Now with this you will be calling the onclick when the user clicks the button, and you will be passing it a string of the color like this:
getOppositeColor('blue')
const markUp = `
<button onclick="myFunction()">Click me</button>
`;
document.body.innerHTML = markUp;
window.myFunction = () => {
console.log('Button clicked');
};

Categories

Resources