I have question regarding the development and testing workflow. I am using Cypress but this topic is suitable for any end to end test.
The question is how do you selecting the elements in the browser?
1, Explicit selectors like data-cy or automation-id on each element or component.
2, Selecting the elements by visible text on the screen and then navigate to specific element by DOM hierarchy.
Every test you write will include selectors for elements. To save yourself a lot of headaches, you should write selectors that are resilient to changes.
Oftentimes we see users run into problems targeting their elements because:
Your application may use dynamic classes or ID's that change
Your selectors break from development changes to CSS styles or JS behavior
Luckily, it is possible to avoid both of these problems.
Don't target elements based on CSS attributes such as: id, class, tag
Don't target elements that may change their textContent
Add data-* attributes to make it easier to target elements
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-cy="submit"
>
Submit
</button>
And then for example clicking to button
cy.get("[data-cy=submit]")
.should("be.visible")
.click()
You can also search for specific text in dom.
cy.get("button")
.should("be.visible")
.contains("Submit")
.click()
Custom commmands commands.js
Cypress.Commands.add("sendBtn", () => {
cy.get("[data-cy=cy_send_btn]")
.should("be.visible")
.click()
})
And in test file
it("Add test description here", function() {
.
.
.
.
cy.sendBtn()
})
Custom command shown above you will be able to use multiple times in other test files for all send buttons. Your tests will be more isolated and efficient.
Related
I have a project in which some components, such as dropdowns, are written in React.
In this case, I can't select an item from the dropdown because the DOM doesn't show what's in that dropdown.
<div class="Select__control css-1s2u09g-control"><div class="Select__value-container css-1d8n9bt"><div class="Select__placeholder css-14el2xx-placeholder" id="react-select-6-placeholder">Select...</div><input id="react-select-6-input" tabindex="0" inputmode="none" aria-autocomplete="list" aria-expanded="false" aria-haspopup="true" role="combobox" aria-readonly="true" aria-describedby="react-select-6-placeholder" class="css-1hac4vs-dummyInput" value=""></div><div class="Select__indicators css-1wy0on6"><div class="Select__indicator Select__dropdown-indicator css-tlfecz-indicatorContainer" aria-hidden="true"><span></span></div></div></div>
How to conduct E2E tests in this case? Can someone explain or share their experience? I did not find information on the Internet. thank you
I looked for this component in the source code, but there are no files with react in the project code, these components are in node_modules, and it is not clear how to access this dropdown
This looks like the react-select control (judging by the classes).
This "hair-trigger" behavior makes it hard to find the options, the list disappears upon any mouse action.
The way I do it is
open the devtools
right-click the select, click inspect to find it in devtools
click the select on the page to open it, repeat a few times to open and close, watch the devtools
an element in devtools appears and disappears when menu is opened and closed. The element has format like this: <div id="react-select-6-listbox">, but the number in the id varies depending on how many selects are used on the page.
We now know the id of the options wrapper, so this is the test code:
cy.get('.Select__control')
.parent() // the top-level of react-select
.click() // open the list
cy.get('[id^="react-select"][id$="listbox"]') // the injected options wrapper
.within(() => {
cy.contains('5 - May').click() // select an option
})
cy.get('.Select__control')
.find('.Select__placeholder')
.should('contain', '5 - May') // verify selected text
If the 5 - May text is unique on the page you can just select it after opening the dropdown, but using .within() on the wrapper is safer.
I've been working with Cypress for a few weeks and I'm starting to need to more complex selecting. Right now I am failing to understand how to chain selectors to get what I need.
Here is a simplified version of the HTML I am looking at:
<my-application-list-item test-hook="applicationListItem">
<div>
<!---->
<div>
<header>
<a test-hook="name" href="/build/applications/iVANKl9Z">E2E Test Item</a>
<my-build-application-menu>
<!---->
<my-dropdown testhook="actionsMenu">
<my-icon-button-settings testhook="openActionsMenu">
<button testhook="openActionsMenu">
The test hooks were placed when we were using Protractor, and there is inconsistency in the hyphenation. But anyway, I want to find and click the openActionsMenu that is inside the applicationListItem which contains the visible text "E2E Test Item".
I thought this would work, based on the docs for get, contains, and find:
cy.get('[test-hook="applicationListItem"]')
.contains("E2E Test Item")
.find('[testhook="openActionsMenu"]')
but this resulted in
Timed out retrying after 4000ms: Expected to find element: [testhook="openActionsMenu"], but never found it. Queried from element: <a.ellipsis.my-info-box__title-link.my-link>
even though running
cy.get('[testhook="openActionsMenu"]')
succeeds. What am I doing wrong?
Use the .contains(selector, content) form of contains().
The subject passed down is the one matching selector.
cy.contains('[test-hook="applicationListItem"]'), "E2E Test Item")
.find('[testhook="openActionsMenu"]')
Experiment a bit, use .then(console.log) to check out the subject at that point
cy.contains('[test-hook="applicationListItem"]'), "E2E Test Item")
.then(subject => console.log(subject) // <my-application-list-item>
.find('[testhook="openActionsMenu"]')
.then(subject => console.log(subject) // <my-icon-button-settings>
"contains" yields the element containing the text, in this case the <a> tag. If you use siblings(), you can force the runner to search in the next element:
cy.get('[test-hook="applicationListItem"]')
.contains("E2E Test Item")
.siblings()
.find('[testhook="openActionsMenu"]')
I'm testing my React application using Selenium IDE and want to add an unique ID ("data-testid" for example) to all elements so I'd be able to select them easily.
So far I encountered libraries which accomplish that by adding some code (react-html-id for example), but I'm looking for a solution which will add these data attribute automatically on build time.
For Example:
Given a component
<SomeComponent /> // returns <span>Hello World<span>
When building the React application, it should be resulted with:
<span data-testid="123">Hello World</span>
I am trying to click a button that is buried in div classes in the code via protractor.
I am pioneering a protractor project for my work and have reached a point where I no longer know what to do. I have a button that is buried in div classes and is not allowing me to click. I have tried using mouseMove to get over to the coordinates of the button, I have tried using the className of the specific button, etc. The button does not have an id. The id is not the issue as I have tried clicking a different button, equally buried in divs, by it's id. I need to know how to get through the layers of divs in order to click the button because the rest of the tests will be dependent on it.
APPLICATION CODE:
::before
<dashboard-label>
<div class="att-topic-analysis-tabs">
<div class="att-button-group">
<button class="btn btn-default btn-lg att-close-topic ng-scope"
role="presentation" tabindex="-1"
ng-click="removeTopic(currentTopic.id)" translate>
Close Topic
</button>
</div>
</div>
PROTRACTOR TEST:
it('Closes Topic Successfully', function(){
//opens the first available topic
openTopic.click();
//checks that the URL contains 'topics' after 5 seconds
browser.wait(proExpect.urlContains('topics'), 5000);
var closeTopic = element(by.className('att-close-topic'));
//browser.wait(proExpect.elementToBeClickable(closeTopicButton), 5000);
console.log(closeTopic);
closeTopic.click();
browser.wait(proExpect.urlContains('home'), 5000);
});
As you can see, the Close Topic button is kind of buried in div classes and the standard click isn't working. Any info would be greatly appreciated
If the closeTopic locator is finding the element, but failing to click it, check to make sure there's only one matching element in the DOM, and that it's visible. My favorite way to check the DOM is just ctrl-F in Chrome inspector and paste the exact CSS that the test is using (.att-close-topic). And to check that what it's getting is visible, use
console.log(closeTopic.isDisplayed());
This can be a big gotcha in protractor, because it doesn't fail (only warns) when there are multiple matches on the page, and it defaults to the first match rather than the first visible match, which drives me nuts, because it's very rare that you want to do anything with a non-visible element on the page.
This will be partly opinion, but just to add a layer to the conversation...
Sometimes the solution to locating a troublesome element on the page is to go back to the developers and make the page more testable. I've seen testers spend hours or days crafting brilliant workarounds to access a stubborn element, and the end result was a fragile, complicated end-to-end test (and aren't they fragile enough already?).
Sometimes a 5-minute conversation with a developer can result in a quick change in the production code (e.g. add a unique ID) that avoids all that effort and yields a much better result, more stable, more simple. But this requires open conversation between the dev and test team, and a culture that values testing as a primary activity enough to make those testability changes to production code that is otherwise working just fine.
This is what you want to read to help you debug why your test doesn't work.
Also, you might want to start adopting await/async since the control flow will go away in the future.
http://www.protractortest.org/#/debugging
try this
var closebutton=element(by.css("[ng-click="removeTopic(currentTopic.id)"]"),
EC = protractor.ExpectedConditions;
Waits for the element to be clickable.checks for display and enable state of button
browser.wait(EC.elementToBeClickable(closebutton), 10000);
now use : closebutton.click();
I'm new on React (I more at ease w/ jQuery or AngularJS). I have a special case and I don't find a good way to resolve it...
My app contains an area which is like a "document viewer". It loads an HTML content from the backend (via API, using Fetch) and inject it in the "viewer" component. The HTML content loaded looks like an "university report" (it's just a formatted text, only <span> and <p> with class="..." attributes, nothing more).
Ex : <p>Lorem ispum <span>some text</span> loreb bis <span>ipsum</span></p> ...
I load the content, and inject it this way in the render() of my component <Viewer> :
<div dangerouslySetInnerHTML={ getFreshlyLoadedHTML() } />
Easy, it works just fine !
But... Now, I want to inject some "interactive" components in the loaded HTML. For example, some button to give a feedback etc. The API must decide where to place the component between the words/nodes of the formatted text (HTML).
Ex :
<p> Lorem ispum <span>some text</span>
loreb bis <span>ipsum</span>
<MyFeedbackButton paragraph="1.3"/>
</p><p>Other Lorem Ipsum<p><span>...</span>
There, I'm stucked because I cannot use dangerouslySetInnerHTML if there are components inside the loaded HTML...
First attempt : I've tried modifying the API, and instead of sending the HTML in a string to the app, I send a custom JSON structure that represents almost the final JSX structure that I want. Then, in my react page, the render function only have to parse the JSON and build the JSX (here, a JsFiddle example if it's not clear : https://jsfiddle.net/damienfa/69z2wepo/34536/ )
It works, but I can't believe it's the good way...
I see a major problem : all the HTML node (span, p...) that I build from the render function are referenced by reactJs, is it really necessary ? Mostly, there are "dead" nodes (I mean, dom node that won't never changed, this is static formatted text).
Just take a look a all those "data-reactid" on nodes that never will be interactive...
What would be your advice on that case ?
What about my attempt with a JSON-structure sent by the API ?
Is there a way to say to react "do not reference that element" ?
Do you clearly see a better solution to my problem ?
Your current workflow is not very secure and subject to many potential errors and open doors, especially concerning code injection ...
The overload due to react tracking the nodes is not an issue, React could track 10 000 nodes and not have a problem (well actually on many of my apps React has more than 100 000 nodes to care about and it still rurns perfectly).
I see different solutions here:
If there are only 3 or 4 possibilities of dynamic components and order, you might have components like "templates" to which you would simple send text arguments. This is the safest and easiest option.
If it doesn't suit your use-case but the JSON file can contain only a limited set of components, the components should be located in your main app, and then rendered with custom props from the JSON. Actually given the structure of data you could consider using xml instead of json and build a xml tree that you would parse and render. Only components from your white list would be rendered and it would limit drastically the potentials security issues. If needs quite some work on the XML parser though.
If the JSON file can contain many many different and unpredictable components or if the behaviour of those components is largely dynamic and independant of your app, you might as well consider using an iframe, with its own JS and HTML, so that this part of the code is isolated from the rest.
Try using an inline anonymous function within the inner content from within React using JSX. It works! Just be careful about how you wire up the data so there isn't a route where a user can inject HTML from an input or text field.
<div className="html-navigation-button">{(() =>
{
const CreateMarkup = ( sNavItemName :string ) => {
return {__html: sNavItemName };
}
var sTextToAddHtmlTo = props.nextNavItem.name.toString();
sTextToAddHtmlTo = sTextToAddHtmlTo.replace( "/", "/<wbr>" );
return (
<div dangerouslySetInnerHTML={CreateMarkup( sTextToAddHtmlTo )} >
</div>
);
})()}
</div>
I didn't override the React internals of 'render()', but only used a React Component with props wiring to pass down data to it for rendering.
I added the hook for 'dangerouslySetInnerHTML' deep within the return content of the React Component so there would be no easy way to intercept and manipulate it.
As such, there is no 100% guarantee on safety, but that's where adding good security to web services, databases, and use of CORS and CORB would be helpful to lock down security risks.