Reputation: 341
Summary of problem: I'm writing several test suites (using Jest and Puppeteer) to automate tests of my AngularJS app's home page. One of the tests I'd like to automate is filling out a form with several md-select
fields and I'm running into some trouble with automating the selection of a particular md-option
. For some inexplicable reason, it seems that Puppeteer just can't find the option after clicking md-select
to open the options dialog. Here is what my html looks like so you can get a sense of what I'm dealing with:
Disclaimer: In order to provide this community with a minimal, reproducible example I chose not to copy and paste my original code by instead wrote up a simpler example. So if you find a typo, I can assure you that is not the source of my problem because I have checked my original code very carefully for typos. I'm sorry in advance for any typos in the following code. I was vigilant but I am human so I make mistakes.
<!-- index.html -->
<html>
<body ng-app="myApp" ng-controller="myCtrl">
<form name="myForm">
<md-dialog-content>
<md-input-container id="animalSelect">
<md-select>"Kittens"</md-select>
</md-input-container>
<md-input-container id="nameSelect">
<md-select>"Cosmo"</md-select>
</md-input-container>
</md-dialog-content>
</form>
<!-- this dialog appears when first <md-select> is clicked -->
<div class="md-select-menu-container">
<md-select-menu>
<md-content>
<md-option value="Kittens">Kittens</md-option>
<md-option value="Puppies">Puppies</md-option>
</md-content>
</md-select-menu>
</div>
<!-- this dialog appears when second <md-select> is clicked -->
<div class="md-select-menu-container">
<md-select-menu>
<md-content>
<md-option value="Cosmo">Cosmo</md-option>
<md-option value="Scout">Scout</md-option>
</md-content>
</md-select-menu>
</div>
</body>
</html>
Background: I'm using Jest (v24.8.0) as my testing framework. I'm using Puppeteer (v1.19.0) to spin up and control a headless Chromium browser.
My broken code:
// index.spec.js
test('submit form', async() => {
let formSelector = 'form[name="myForm"]';
let animalSelector = '#animalSelect md-select';
let animalOptionSelector = 'md-option[value="Puppies"]';
let nameSelector = '#nameSelect md-select';
let nameOptionSelector = 'md-option[value="Scout"]';
await page.waitForSelector(formSelector, {timeout: 3000});
await page.waitForSelector(animalSelector, {timeout: 3000});
await page.waitForSelector(nameSelector, {timeout: 3000});
// this works fine
await page.click(animalSelector);
await page.waitForSelector(animalOptionSelector, {timeout: 3000});
await page.click(animalOptionSelector);
// this fails: Jest says nameOptionSelector is not in the DOM (see error below)
await page.click(nameSelector);
await page.waitForSelector(nameOptionSelector, {timeout: 3000});
await page.click(nameOptionSelector);
});
Outcome of running my test:
After I run the above test, Jest says that 'submit form'
has failed and provides this error message:
Node is either not visible or not an HTMLElement
await page.click(nameOptionSelector);
^
This means that when Puppeteer went to click nameOptionSelector
, it couldn't find it in the DOM. But page.waitForSelector(nameOptionSelector, {timeout: 3000})
has seemingly succeeded, which tells me that nameOptionSelector
WAS in the DOM at the time that page.click
was called.
Troubleshooting attempts:
The only way I can get 'submit form'
to pass is by adding this line above page.click(nameOptionSelector)
:
// this line tells the test to pause for 1 second before clicking
await page.waitFor(1000);
... but this is a bad solution because it's imprecise and doesn't fix the source of the problem.
I've also read about Puppeteer's page.select method but that won't work in my case because I'm not using the native HTML <select>
elements.
Question: Do any of you Jest/Puppeteer hackers out there know of a solution for this?
Upvotes: 3
Views: 2090
Reputation: 183
considering its angular app i assume there is some style disappear property
like style="display: none"
which is handled by angular directive (i am not familiar with angular so i might be wrong)
In that case, your click triggers event which changes state of button -> makes it appear/disappear (it takes some time, so that's why page.waitFor
function seems to solve problem).
page.waitForSlector(nameOptionSelector)
resolves immediately because selector already exists (you can check if it is true by running document.querySelector('md-option[value="Scout"]')
in console, before clicking on md-select
and after it. In both cases you should get document element)
however page.click()
fails when it tries to click on element which is not visible. So you can try to await other selector which will have angular property properly set. Probably the one which states if element is visible or not.
btw if your whole app is written in angular, consider other framework for testing -> eg. protractor as it is dedicated for angular apps
Upvotes: 1
Reputation: 15575
Unfortunately, I also faced similar problem as you and await page.waitFor(3000)
in my case mostly helped me out. Here is a snippet of code I use to keep scrolling down until I reach the bottom of pages where dynamic content keeps getting appended:
let preCount = 0;
let postCount = 0;
do {
preCount = await getCount(page);
await scrollDown(page);
await page.waitFor(3000);
postCount = await getCount(page);
} while (postCount > preCount);
await page.waitFor(3000);
When I tried to lower the wait from 3 seconds to 1 or 2 seconds, I sometimes would not get to the bottom of the page. I know this because I disabled headless mode when I launched puppeteer so I can see what is happening in the browser:
{
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
}
Upvotes: 1