Puppeteer - how to click on an inner element - reactjs

I'm using Puppeteer to do e2e tests for an app built with React and Material-UI.
In my login form I'm trying to click on the login button but I get loginBtn.click is not a function - that's because Material UI (version 0.21.0) is wrapping the <RaisedButton> with an extra div so I can only reach the outer element. I need a way to access the inner element and then simulate the click event.
I've tried this:
const loginBtn = await page.$eval(
'.login-form-button',
(element) => element.innerHTML
);
await loginBtn.click();
But it seems like element.innerHTML is just a text and not clickable.
Any ideas how can I get the inner element?

page.$eval selects an element (.login-form-button) and passes that element to the second argument ((element) => element.innerHTML), which will in your case return the HTML of the node as a string. Therefore loginBtn is not a button but a string. To click an element you might want to use page.click.
If you have a more complex term to execute I recommend using page.evaluate. In your case you could use it like this to query the element and click its first child element:
await page.evaluate(() => {
document.querySelector('.login-form-button').firstElementChild.click();
});

Related

#testing-library/React : Clicking outside of component not working

I'm using react testing library to test my component built with FluentUI.
Here is link:
https://codesandbox.io/s/keen-borg-2tqmj?file=/src/App.spec.js
The code is basically a pasted snippet of the example code of Dialog component from FluentUI docs site. The behavior that I'm testing is:
User opens the dialog
User clicks outside of the dialog
onDimiss prop of the component should be fired.
It works when I'm playing with it manually however it seems that I failed to simulate a click outside of the component with testing library.
I tried using userEvent.click(document.body) as mentioned in this post but got no luck
Does anyone has any idea how to make test work?
It is not working because the Dialog component is not listening for the onClick event on the body, so what you need to do in this case is to find the actual element that is being clicked, observing the dom you'll find that the overlay is a div with some overlay classes on it.
<div
class="ms-Modal is-open ms-Dialog root-94"
role="document"
>
<div
aria-hidden="true"
class="ms-Overlay ms-Overlay--dark root-99"
/>
The problem now is to find a way to select it. Unfortunately, you cannot select elements in RTL by their className, so you need to use another selector; in this case, we can get the parent element by the role and then access the first child.
const onDismiss = jest.fn();
const { getByRole } = render(<App onDismiss={onDismiss} />);
UserEvent.click(screen.getByText("Open Dialog"));
const document = getByRole("document");
UserEvent.click(document.firstChild);
expect(onDismiss).toHaveBeenCalled();
https://codesandbox.io/s/hungry-joliot-tjcph?file=/src/App.spec.js

How to click unrendered virtualized element in TestCafe

I'm using react-virtualized for a lengthy (1000+) list of items to select from. And I'm trying to set up an end to end test that requires clicking on one of the elements that are not currently rendered.
Ordinarily, I'd simply use something like:
await t.click(
ReactSelector('ListPage')
.findReact('ListItem')
.nth(873) // or .withText(...) or .withProps(...)
)
But because only a small subset of the ListItems are rendered, TestCafe fails to find the desired element.
I've been trying to figure out how to use TestCafe's ClientFunction to scroll the list container so the desired ListItem is rendered.
However, I'm running into a few issues:
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop? Or do I have to re-query the element via the DOM directly?
Due to the ListItems being different heights, the scroll position is not a simple calculation of index x item height. How can I keep updating/scrolling within this function until the desired Selector is visible?
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop?
There is a way to put Selector into the Client Function. Please refer to this example in the TestCafe documentation.
How can I keep updating/scrolling within this function until the desired Selector is visible?
You can use the TestCafe exist property to check if the element is rendered or not. The following example demonstrates the approach:
import { Selector } from 'testcafe';
fixture`Getting Started`.page('https://bvaughn.github.io/react-virtualized/#/components/List')
test('Test 1', async t => {
const dynamicRowHeightsInput = Selector('._1oXCrgdVudv-QMFo7eQCLb');
const listItem = Selector('._113CIjCFcgg_BK6pEtLzCZ');
const targetItem = listItem.withExactText('Tisha Wurster');
await t.click(dynamicRowHeightsInput);
while (!await targetItem.exists) {
const currentLastRenderdItemIndex = await listItem.count -1;
const currentLastRenderdItemText = await listItem.nth(currentLastRenderdItemIndex).textContent;
const currentLastRenderdItem = await listItem.withExactText(currentLastRenderdItemText);
await t.hover(currentLastRenderdItem);
}
await t
.hover(targetItem)
.click(targetItem);
await t.debug();
});
To scroll the list container I used the hover action with a last rendered listItem as a target element.

ReactJS & Material UI - ClickawayListener is not working properly in the Shadow DOM

I'm trying to build Micro Front End applications using ReactJS and Material UI framework. As a part of it, I was trying to embed a react application into the main React App using ShadowDOM.
I got the application running and working except when opening a popover, dialogs, modal, or date picker the ClickAwayListener is not functioning as expected meaning not closing.
Please suggest a way to fix this or show me a workaround to get the application running.
Code Sandbox
Found a workaround:
First, create a click listener on your shadow root to trigger a CustomEvent called closeModal.
const shadowRoot = document.getElementById('root').attachShadow({open: true});
let mountPoint = document.createElement('div');
mountPoint.id = "portal";
mountPoint.addEventListener('click', (e) => {
let event = new CustomEvent('closeModal',{bubbles: true, cancelable: false});
shadowRoot.dispatchEvent(event);
});
ReactDOM.render(themeProvider, mountPoint);
Then, when the popover or modal or date picker or dialog opens, create another event listener
document.getElementById('root').shadowRoot.addEventListener('closeModal', this.handleClose);
Once the modal is closed by the handleClose function, remove the event listener
document.getElementById('root').shadowRoot.removeEventListener('closeModal', this.handleClose);
Thats it.
I don't see where ClickAwayListener is being used in your CodeSandbox link, but I would guess that this issue is because you didn't wrap the inner element in an element that can accept a ref. Try wrapping the child element in a <div> and see where that gets you. :)

Mimic React custom component

I have a custom Reactjs component to display Pagination with next/previous buttons at the bottom of a grid. Now, the business needs to display the same component on top of the grid as well. How to display the previous /next button events based on the input provided in prev/next buttons at the bottom of the grid?
I tried using javascript innerHTML to mimic the behaviour. It works only with the display. It does not attach the event listener of the buttons. I tried even with
document.querySelector.addEventListener('click', ()=>{console.log('test')})
It does not work. Is there a better way to do with react.
I am going to just add some more content to Shmili Breuer answer.
If i understood you correctly you have 2 navigations, one at the top one at the bottom. The way you connect them would be through a state of you component, or a parent component if you are using functional component to render pagination stuff. So if you change the state it will reflect on both of your navigations. Also you can use only one function here, by passing a parameter, im gonna copy a code from before mentioned answer.
// first create a function
nextFunction = (condition) => {
if(condition){
this.setState(prevState=>({
page: prevState.page-1
}))
} else {
this.setState(prevState=>({
page: prevState.page+1
}))
}
}
// then use it in your button
<button onClick={() => this.nextFunction(some condition)}>Next</button>
Just put that component on top and bottom
<Grid>
<Pagination />
{...someOtherComponents}
<Pagination />
</Grid>
it's ok in react. Optimization that you want to do is overhead.
In react you would add an onClick attribute to an element you want to handle a click on.
Something like this
// first create a function
nextFunction = () => {
do next functionality....
}
// then use it in your button
<button onClick={() => this.nextFunction()}>Next</button>
This way you can have both top and bottom pagination call the same function.
Hope this helps

Chain Selectors are not working properly in webdriver.io

i have my dom like the below , i am able to get the first element
this is what i am trying to get the 2nd and 3rd element but getting null
here is the actual code in my cucumber step definitions.
When('I click on an option', async function() {
const items = '.entry';
await browser.waitForExist(items);
await browser.click('.entry:nth-child(2)');
});

Resources