Pupeteer click on bootstrap card

559 Views Asked by At

I'm trying to use puppeteer to write react tests but having hard time click on elements, except simple buttons and links. I'm trying to click on a bootstrap (reactstrap) card (or the image/text inside it) to add an item to a cart. Then I have to check that item is added to the cart by checking if the item is on data table (component). This is the screenshot of how the page looks like. enter image description here

This is the html part of product on browser

<div class="col-sm-2">
   <a class="cartCard card">
      <img alt="SkinnyPop Original Popcorn" src="https://d2d8wwwkmhfcva.cloudfront.net/600x/filters:fill(FFF,true):format(jpg)/d2lnr5mha7bycj.cloudfront.net/product-image/file/large_089fa30f-31a6-4d53-ae00-1dcd2d5f8d81.jpeg" class="cartCarImage card-img">
      <div class="cartCardBody card-body">
         <p data-tip="SkinnyPop Original Popcorn" data-for="Foods1" class="card-text" currentitem="false">SkinnyPop Original Popcorn</p>
         <div class="__react_component_tooltip t20e8f3c7-872c-447a-b947-00036aa0a356 place-top type-light" id="Foods1" data-id="tooltip" style="left: 152px; top: 317px;">
            <style>
               .t20e8f3c7-872c-447a-b947-00036aa0a356 {
               color: #222;
               background: #fff;
               border: 1px solid transparent;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-top {
               margin-top: -10px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-top::before {
               border-top: 8px solid transparent;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-top::after {
               border-left: 8px solid transparent;
               border-right: 8px solid transparent;
               bottom: -6px;
               left: 50%;
               margin-left: -8px;
               border-top-color: #fff;
               border-top-style: solid;
               border-top-width: 6px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-bottom {
               margin-top: 10px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-bottom::before {
               border-bottom: 8px solid transparent;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-bottom::after {
               border-left: 8px solid transparent;
               border-right: 8px solid transparent;
               top: -6px;
               left: 50%;
               margin-left: -8px;
               border-bottom-color: #fff;
               border-bottom-style: solid;
               border-bottom-width: 6px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-left {
               margin-left: -10px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-left::before {
               border-left: 8px solid transparent;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-left::after {
               border-top: 5px solid transparent;
               border-bottom: 5px solid transparent;
               right: -6px;
               top: 50%;
               margin-top: -4px;
               border-left-color: #fff;
               border-left-style: solid;
               border-left-width: 6px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-right {
               margin-left: 10px;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-right::before {
               border-right: 8px solid transparent;
               }
               .t20e8f3c7-872c-447a-b947-00036aa0a356.place-right::after {
               border-top: 5px solid transparent;
               border-bottom: 5px solid transparent;
               left: -6px;
               top: 50%;
               margin-top: -4px;
               border-right-color: #fff;
               border-right-style: solid;
               border-right-width: 6px;
               }
            </style>
            SkinnyPop Original Popcorn
         </div>
      </div>
   </a>
</div>

this is the html of data table cart item

<div id="cartTable">
   <div class="sc-fzoyTs colVQD">
      <div class="sc-fzoXWK iDAPfj">
         <div class="sc-AxheI cBBklH rdt_Table" role="table">
            <div offset="250px" class="sc-fzqNqU jvuxKt rdt_TableBody" role="rowgroup">
               <div id="row-1" role="row" class="sc-fzokOt egXWdh rdt_TableRow">
                  <div id="cell-YTyZ4X_xd-1" role="cell" data-tag="___react-data-table-allow-propagation___" class="sc-fzozJi sc-fzoLsD sc-fzpans bmdIeB rdt_TableCell">
                     <div>1</div>
                  </div>
                  <div id="cell-C1s2WxpKFG-1" role="cell" data-tag="___react-data-table-allow-propagation___" class="sc-fzozJi sc-fzoLsD sc-fzpans fJfkQJ rdt_TableCell"><img src="https://d2d8wwwkmhfcva.cloudfront.net/600x/filters:fill(FFF,true):format(jpg)/d2lnr5mha7bycj.cloudfront.net/product-image/file/large_089fa30f-31a6-4d53-ae00-1dcd2d5f8d81.jpeg" width="36" class="float-left rounded-circle"></div>
                  <div id="cell-emxpVgIyBK-1" role="cell" data-tag="___react-data-table-allow-propagation___" class="sc-fzozJi sc-fzoLsD sc-fzpans drmANF rdt_TableCell">
                     <div class="cartItemText">SkinnyPop Original Popcorn</div>
                  </div>
                  <div id="cell-G8l-QeN6dF-1" role="cell" data-tag="___react-data-table-allow-propagation___" class="sc-fzozJi sc-fzoLsD sc-fzpans bpmFTx rdt_TableCell">
                     <div>
                        <div><span>$18.31</span></div>
                        <div class="cartItemLinestrike" style="display: none;"><span>$18.31</span></div>
                     </div>
                  </div>
                  <div id="cell-ZmDGwu4kJK-1" role="cell" data-tag="___react-data-table-allow-propagation___" class="sc-fzozJi sc-fzoLsD sc-fzpans iIVAzx rdt_TableCell">
                     <div>
                        <button data-garden-id="buttons.button" data-garden-version="8.13.0" type="button" class="sc-AxiKw cSylWS">
                           <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trash-alt" class="svg-inline--fa fa-trash-alt fa-w-14 " role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="font-size: 14px;">
                              <path fill="currentColor" d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"></path>
                           </svg>
                        </button>
                     </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>
</div>

I'm trying to select them by this function I have. It works with button and link. It doesn't work with product and cart item.

 const clickByElement = async (page, text, element) => {
  let escapedText = escapeXpathString(text);
  let linkHandlers = [];
  if (element === "button")  {
    linkHandlers = await page.$x(`//button[contains(text(), ${escapedText})]`);
  } else if  (element === "link")  {
    linkHandlers = await page.$x(`//a[contains(text(), ${escapedText})]`);
  } else if  (element === "product")  {
     linkHandlers = await page.$x(`//img[contains(., ${escapedText})]`);
    //linkHandlers = await page.$x(`//img[contains(text(), ${escapedText})]`);
    //linkHandlers = await page.$x(`//div[contains(text(), ${escapedText})]`);
    //linkHandlers = await page.$x(`//a[@class='cartCard card']/div[contains(text(), ${escapedText})]`);
  } else if  (element === "cartitem")  {
    linkHandlers = await page.$x(`//div[contains(@class, 'cartItemText') and contains(., ${escapedText})]`);
  }
  if (linkHandlers.length > 0) {
    await linkHandlers[0].click();
  } else {
    throw new Error(`Link not found: ${text}`);
  }
  await timeout(1000);
};
2

There are 2 best solutions below

1
On

Something is not OK with the XPath expressions you've defined, I give an example. Instead of await page.$x(`//a[@class='cartCard card']/div[contains(text(), ${escapedText})]`) you should try something like this (make sure you wait for the elements to be appeared):

await page.waitForXPath('//*[@class="cartItemText" and contains(.,"Skinny")]')
const linkHandlers = await page.$x('//*[@class="cartItemText" and contains(.,"Skinny")]')

await linkHandlers[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. You can test custom built XPath expressions here as well. E.g.: $x('//*[@class="cartItemText" and contains(.,"Skinny")]')[0].innerText should show the the text that you've expected to click on ("SkinnyPop Original Popcorn"), 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 to find more appropriate selectors for your scripts.

0
On

I solved the selection like this

  linkHandlers = await page.$x(`//img[contains(@alt, ${escapedText})]`);