how to wait for load newly created elements - puppeteer - puppeteer

I want to get the sizes of the product from the adidas.com site using Puppeteer. The sizes are not loaded in the initial loading and the HTML code is as follows:
<div class="sizes___3Stmf">
<div class="gl-label size___TqqSo" data-testid="size-placeholder"><span class="notranslate placeholder___yXpD2">AAA</span></div>
</div>
But after full loading, the elements will change as below and the sizes will be displayed:
<div class="sizes___3Stmf gl-vspace" data-auto-id="size-selector">
<button class="gl-label size___TqqSo selected___2CqxQ"><span>36</span></button>
</div>
How can I wait for the changed elements to load and then get it?
My code is as follows. I even tried using the waitForSelector() but it didn't work.
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitForSelector('.sizes___3Stmf button');
const outputApp = await page.$(".sizes___3Stmf");
const outputHtml = await page.evaluate((el) => el.innerHTML, outputApp);
console.log(outputHtml);
I get the following error by running the above code:
timeouterror: waiting for selector `.sizes___3stmf button` failed: timeout 30000ms exceeded.

You should rely on 100% stable element attributes. sizes___3Stmf class name doesn't look like one of them.
Try to wait for div with attribute data-auto-id:
await page.waitForSelector('div[data-auto-id="size-selector"] button');

Related

Wrong colors displayed after React renders div to DOM

I was writing some code to map random colors to cells in row.
const COLORS = ['blue', 'green', 'orange', 'red', 'purple', 'yellow'];
const createRandomColors = () => {
const randomColors = [];
for (let i = 0; i < COLORS.length; i++) {
const randomColor = COLORS[Math.floor(Math.random() * COLORS.length)];
randomColors.push(randomColor);
}
return randomColors;
}
const App = () => {
const row = useMemo(createRandomColors, []);
console.log(row);
const cells = useMemo(() => row.map((cell, cellIndex) =>
<div key={cellIndex} style={{ backgroundColor: cell }}>{cellIndex}</div>
), [row]);
cells.forEach(cell => console.log(cell.props.style.backgroundColor));
return (
<div className="app">
<div className="row">
{cells}
</div>
</div>
);
};
export default App;
The problem is that after rendering div elements they have completely different inline style background-color that was specified when mapping divs.
Please see CodeSandBox and take a look at console log and real results rendered.
Why is this happening?
The reason this appears wonky is related to using <StrictMode> in your index.html file. If you remove <StrictMode> you'll see your code works the way you expect. But don't do that, you really do have a bug.
React wants functional components to be idempotent, meaning they do not have side effects. To help you catch side-effects, React will call your code twice back to back when it renders. see strict mode By doing this, it helps uncover subtle issues like the one you're currently experiencing.
One solution is to create the random colors once using useEffect(). Another is to generate the colors outside the functional component.
UPDATE
Please mark the answer as 'accepted' if it solves your issue. You are correct. useMemo will save the computation so it will not be re-computed unless dependencies change. However, react is purposely calling your code twice (in debug mode only) to help you catch unintentional side effects in your classes or hooks. When using strict mode, it's as if you have two of the component instead of one. i.e.
/* StrictMode in debug */
<StrictMode>
<App/>
</StrictMode>
/* ... be like this: */
<>
<App/>
<App/>
</>
If you (temporarily) remove the <StrictMode> tag you'll see your code works as expected. And if you add code that causes your component to render again (e.g. a click counter) your useMemo should prevent the cells from being regenerated each render.
Add a console log to print every time createRandomColors() is called. Since your code is being called twice, you should see the debug log appear twice, but you don't. Why not? React surpasses the console.log the 2nd time it calls your code.
At the top of your code (line 3) add const log = console.log, then replace everywhere you use console.log with just log and you'll have the full picture of what's occurring.
Keep experimenting. We've all been here.

Why does await page.Evaluate() return 'undefined'?

I'm trying to write a deploybot with nodejs, but when trying to navigate to the environments page it fails to find this button.
Here is the snippet of code:
//wait until element with unique id containing the environments button is there
await page.waitForSelector('#formatstring_widget_formatstring_14');
//check if the element actually exists, so that i can log that.
const envElement = await page.$('#formatstring_widget_formatstring_14');
if (envElement != null) {
console.log('env element exists');
} else {
console.log('no env element found');
}
const link = await page.evaluate((env)=> {
env.innerHTML;
}, envElement);
console.log('env= '+link);
If I run this, I get a log of:
'Env element exists'
'Env = undefined'
which means the element exists, but there is no innerHTML? but when I inspect the source code from the page I'm accessing, the
id=#formatstring_widget_formatstring_14 does have inner html
How is this possible?
Here is the source code
<div data-mendix-id="51_37_138" class="mx-name-formatString1 mx-link submenu-item page-nav-9" tabindex="0" id="formatstring_widget_formatstring_14" focusindex="0" widgetid="formatstring_widget_formatstring_14" style="">
<div class="formatstring ">
<a href="https://cloud.home.mendix.com/link/deploy/d22310d5-a10f-437b-93d7-c0ceab21d0c6" class="">
Environments</a>
</div></div>
It might be far easier to use the Deploy API from Mendix to automate deployment. See the API here: https://docs.mendix.com/apidocs-mxsdk/apidocs/deploy-api
Regards,
Ronald

Attempting to click on a button but not working for me

I have the following code:
<div class="pt6-sm pb6-sm ta-sm-r d-sm-flx flx-jc-sm-fe"><button class="mb1-sm css-bz0cep ex41m6f0 secondary" type="button">Cancel</button><button class="ml3-sm mb1-sm css-17hmqcn ex41m6f0 primary" type="button">Save</button></div>
I'm attempting to click on the save button and I'm trying this:
const addBtn = await select(page).getElement('button:contains(Save)');
await addBtn.click()
I have also tried:
const [button] = await page.$x("//div[#class='elements']/button[contains(., 'Save')]");
if (button) {
await button.click();
}
I get an unhandled promise rejection. Any idea why?
Basically you get the UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'click' of undefined until either your selector is not correct / element doesn't exist or it haven't yet appeared on the page.
In this case it seems your xpath selector was not grabbing the element.
That will do the job:
const addBtn = await page.$x('//button[contains(text(), "Save")]')
await addBtn[0].click()
Did you know? If you right click on an element in Chrome DevTools "Elements" tab and you select "Copy": there you are able to copy the exact selector or xpath of an element. After that you can switch to the "Console" tab and with the Chrome api you are able to test the selector's content, so you can prepare it for your puppeteer script. E.g.: $x('//button[contains(text(), "Save")]').textContent should show the text of the button that you've expected to click on, otherwise you need to change on the access, or you need to check if there are more elments with the same selector etc. This may helps you to find more appropriate selectors.

Puppeteer js id element contains

I currently have selected an item in a web page with the xpath:
*[#id="content_gvNewLeads_tccell0_5"]/a
I need to get each one of these elements, there are about 200 on a page. Each id changes slightly.
I've tried this:
const aLink = await page.$x('//id[contains(content_gvNewLeads_tccel)]/a')
I get an error any idea how I can achieve this?
try this XPath instead:
const aLink = await page.$x('//*[contains(#id,"content_gvNewLeads_tccel")]/a')

test case not detecting update in ViewContainerRef

My html uses an ng-template. The template is to create thumbnails.
<ng-template #thumbnailTemplate let-option="option">
<div id="{{option.divId}}"> <!-- top level div of thumbnail. This will have ids thumbnail-1, thumbnail-2 etc.-->
<img id="{{option.imgId}}" src="{{option.imgSrc}}"> <!-- this will have width, height=80-->
<!-- the X button is created using CSS. This will have ids close-button-1, close-button-2. They'll also containn reference to the parent div id (thumbnail-1, thumbnail-2 ) -->
</div>
</ng-template>
The thumbnails gets created when a file is selected from an input element. FileReader sends load event and my event handler is called which should create a thumbnail by adding a view in the container
handleReaderLoaded(event:FileReaderProgressEvent) {
console.log("got load event of file reader ",event);
let thumbnailTemplateViewRef:EmbeddedViewRef<any>;
let imageString = event.target.result;//this will be like data:image/png;base64,ZGQ=ZGF0YTppbWFnZS9wbmc7YmFzZTY0LFpHUT0=
console.log("result from file load: ",imageString);
console.log("consecutive generator is ",this.consecutiveIdGenerator);
//create new ids for div, img and a in the template
++this.consecutiveIdGenerator;
let divId = "thumbnail-"+(this.consecutiveIdGenerator);
console.log("div id "+divId);
let imgId = "img-"+(this.consecutiveIdGenerator);
console.log("img id "+imgId);
let closeId = "close-button-"+(this.consecutiveIdGenerator);
console.log("close Id is "+closeId);
console.log("thumbnail container length was "+this.thumbnailContainerRef.length);
//TODOM - define context as a class so that it can be used in new question and question details
thumbnailTemplateViewRef = this.thumbnailContainerRef.createEmbeddedView(this.thumbnailTemplateRef,{option:{divId:divId,
imgId:imgId,
closeId:closeId,
imgSrc:imageString}});
//store the reference of the view in context of the template. This will be used later to retrive the index of the view when deleting the thumbnail
thumbnailTemplateViewRef.context.option.viewRefId = thumbnailTemplateViewRef;
console.log("thumbnail container length is "+this.thumbnailContainerRef.length);
}
Now I want to test handleReaderLoaded and check that it updates the thumbnailContainerRef by adding thumbnailTemplateViewRef in it.
The spec I have written is
fit('should upload image if user selects an image', () => {
let newPracticeQuestionComponent = component;
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(0);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(0);
let file1 = new File(["foo1"], "foo1.txt");
let reader = newPracticeQuestionComponent.handleFileSelect([file1]);//the callback for FileReader load method is assigned in this function. The callback is handleReaderLoaded
fixture.detectChanges();
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(1);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1);
done(); //wait
console.log('done here');
});
My test case is failing because expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1); is coming out as 0.
What am I doing wrong?
seems, I didn't understand the purpose of done correctly. I thought if I'll use done, the script will automatically wait but it doesn't (as is clear from the following trace)
reading file --> this is in handleFileSelect
context.js:1972 done here -->ths is in handleFileSelect
context.js:1972 got load event of file reader ProgressEvent {isTrusted: true, lengthComputable: true, loaded: 4, total: 4, type: "load", …} --> this is in the callback handleReaderLoaded. So the spec finished before the callback was called.
I done acts as a checkpoint in Jasmine. When Jasmine sees that a spec uses done, it knows that it cannot proceed to the next step (say run next spec) unless the code leg containing done has been called.
I re-wrote the spec to and created the checkpoint using done as follows
it('should upload image if user selects an image', (done) => {
let newPracticeQuestionComponent = component;
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(0);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(0);
let imageThumbnailDiv = fixture.debugElement.query(By.css("#thumbnail-1"));
let imageThumbnailImg = fixture.debugElement.query(By.css('#img-1'));
let imageThumbnailClose = fixture.debugElement.query(By.css('#close-button-1'));
//there should not be any HTML code which contains thumbnail
expect(imageThumbnailDiv).toBeFalsy();
expect(imageThumbnailImg).toBeFalsy();
expect(imageThumbnailClose).toBeFalsy();
let file1 = new File(["foo1"], "foo1.txt");
let reader = newPracticeQuestionComponent.handleFileSelect([file1]);
//file upload is async. so give time for `load` event of FileReader to be triggered and handled
setTimeout(function() {
console.log("in timeout");
fixture.detectChanges();//without this, the view will not be updated with model
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(1);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1);
//the html for thumbnail should be created now
let imageThumbnailDiv2 = fixture.debugElement.query(By.css("#thumbnail-1"));
let imageThumbnailImg2= fixture.debugElement.query(By.css('#img-1'));
let imageThumbnailClose2 = fixture.debugElement.query(By.css('#close-button-1'));
expect(imageThumbnailDiv2).toBeTruthy();
expect(imageThumbnailImg2).toBeTruthy();
expect(imageThumbnailClose2).toBeTruthy();
done();//without done, jasmine will finish this test spec without checking the assertions in the timeout
}, 2000);
//if done is not use, jasmine will just finish the current spec without checking any assertions
});