Search code examples
javascriptarraysnode.jsnightwatch.jsbrowser-automation

How do I assign a key value pair to an object array with a forEach loop in javascript nightwatch.js test automation script?


//I created a loop to append an index number [1-16] to the end of an xpath selector (these are selectors for li items on a website, they're all the same except the index number on the end.) Then I want to assign that selector to an existing object array as a value to a key named 'link'. Then I want to call a custom command to loop through the array and click each key value pair. This is for the purpose of clicking the links(xpath selectors) and verifying the automation lands on the correct page which I verify with the url. Each object in the array has 2 key value pairs, link and url. I tried doing it where I just add the selectors to a regular array and it works fine in that manner. But when I try to add the selector to an array as part of an object I get errors. My code is below:

//Here is where I want to assign the newly concatenated xpath selector to my object array. Iv'e tried map, push, concat, assign and every other function I could find associated with arrays:


for (let i = 0; i < 17; i++) { 
    abtArr[i].concat({link: abtSel + `[${i}]`})
} 

abtArr.forEach(item => {
    aosPage
        .click(item.link) 
        .verify.urlContains(item.url)
})


//object array (abtArr)

module.exports = [
    {
        link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div/div[2]/div[1]/div/ul/li[1]',
        url: 'https://www.aos.org/about-us/aos-membership.aspx'
    }, 
    {
        link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div[1]/div[2]/div[1]/div/ul/li[2]',
        url: 'https://www.aos.org/about-us/lindleyana-magazine.aspx'
    },


]

A Code Snippet (minimal reproducible example):

function getElementByXpath(path) {
  return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

/*
const abtArr = [
    {
        link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div/div[2]/div[1]/div/ul/li[1]',
        url: 'https://www.aos.org/about-us/aos-membership.aspx'
    }, 
    {
        link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div[1]/div[2]/div[1]/div/ul/li[2]',
        url: 'https://www.aos.org/about-us/lindleyana-magazine.aspx'
    },
];
*/

// const abtSel = '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div/div[2]/div[1]/div/ul/li';
const abtSel = '//*[@id="target"]/ul/li';
const abtArr = [];

for (let i = 1; i < 11; i++) { 
    const xPath = `${abtSel}[${i}]/a`;
    const linkTag = getElementByXpath(xPath);
    const someUrl = linkTag ? linkTag.href : null;
    abtArr.push({link: xPath, url: someUrl})
} 

console.log(abtArr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<div id="target">
  <ul>
    <li><a href="example.com/1">Link</a></li>
    <li><a href="example.com/2">Link</a></li>
    <li><a href="example.com/3">Link</a></li>
    <li><a href="example.com/4">Link</a></li>
    <li><a href="example.com/5">Link</a></li>
    <li><a href="example.com/6">Link</a></li>
    <li><a href="example.com/7">Link</a></li>
    <li><a href="example.com/8">Link</a></li>
    <li><a href="example.com/9">Link</a></li>
    <li><a href="example.com/10">Link</a></li>
  </ul>
</div>


Solution

  • If I understand correctly, your problem is sort of the opposite of this:

    "You create 16 xPaths on the fly like 'div/ul/li[n]' where n = 1 through 16 and with each one, you select that element and check what the hyperlink href is, whatever the hyperlink is you save this to the URL property of your object and push that object to the array like [{link: 'div/ul/li[n]', URL: 'example.com/[n]'}]" (see the opposite at the bottom)

    Here is a Code Snippet (minimal reproducible example) with HTML & JS (same as I put in your question as an example):

    Also, it looks like you are using node and Nightwatch, you should tag these in the question (which I did now - should help the right people to find the question etc.) and add at least the minimal import statements and initial data etc.

    Is this the sort of thing you are trying to do? (But of course, this example is client-side only, so it's not 1-for-1 but at least it should give you an idea)

    function getElementByXpath(path) {
      return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }
    
    /*
    const abtArr = [
        {
            link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div/div[2]/div[1]/div/ul/li[1]',
            url: 'https://www.aos.org/about-us/aos-membership.aspx'
        }, 
        {
            link: '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div[1]/div[2]/div[1]/div/ul/li[2]',
            url: 'https://www.aos.org/about-us/lindleyana-magazine.aspx'
        },
    ];
    */
    
    // const abtSel = '//*[@id="aspnetForm"]/div[4]/section/div[1]/div/div[2]/div/div[2]/div[1]/div/ul/li';
    const abtSel = '//*[@id="target"]/ul/li';
    const abtArr = [];
    
    for (let i = 1; i < 11; i++) { 
        const xPath = `${abtSel}[${i}]/a`;
        const linkTag = getElementByXpath(xPath);
        const someUrl = linkTag ? linkTag.href : null;
        abtArr.push({link: xPath, url: someUrl})
    } 
    
    console.log(abtArr);
    /*.as-console-wrapper { max-height: 100% !important; top: 0; }*/
    <div id="target">
      <ul>
        <li><a href="example.com/1">Link</a></li>
        <li><a href="example.com/2">Link</a></li>
        <li><a href="example.com/3">Link</a></li>
        <li><a href="example.com/4">Link</a></li>
        <li><a href="example.com/5">Link</a></li>
        <li><a href="example.com/6">Link</a></li>
        <li><a href="example.com/7">Link</a></li>
        <li><a href="example.com/8">Link</a></li>
        <li><a href="example.com/9">Link</a></li>
        <li><a href="example.com/10">Link</a></li>
      </ul>
    </div>

    Output:

    [
      {
        "link": "//*[@id=\"target\"]/ul/li[1]/a",
        "url": "https://stacksnippets.net/example1.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[2]/a",
        "url": "https://stacksnippets.net/example2.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[3]/a",
        "url": "https://stacksnippets.net/example3.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[4]/a",
        "url": "https://stacksnippets.net/example4.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[5]/a",
        "url": "https://stacksnippets.net/example5.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[6]/a",
        "url": "https://stacksnippets.net/example6.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[7]/a",
        "url": "https://stacksnippets.net/example7.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[8]/a",
        "url": "https://stacksnippets.net/example8.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[9]/a",
        "url": "https://stacksnippets.net/example9.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[10]/a",
        "url": "https://stacksnippets.net/example10.com"
      }
    ]
    

    So if your problem is the opposite, i.e. then:

    "You have 16 xPaths pre-defined like 'div/ul/li[n]' where n = 1 through 16 and with each one, you have a pre-defined URL you expect this list item / link tag to hold, you want to then click this link and you assert that the link you navigate to is the one that matches the xpath link in the array like [{link: 'div/ul/li[n]', URL: 'example.com/[n]'}]"

    You want to use Node and Nightwatch for this.

    You could do it like this, I presume:

    function getElementByXpath(path) {
      return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }
    
    const abtArr = [
      {
        "link": "//*[@id=\"target\"]/ul/li[1]/a",
        "url": "https://stacksnippets.net/example1.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[2]/a",
        "url": "https://stacksnippets.net/example2.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[3]/a",
        "url": "https://stacksnippets.net/example3.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[4]/a",
        "url": "https://stacksnippets.net/example4.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[5]/a",
        "url": "https://stacksnippets.net/example5.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[6]/a",
        "url": "https://stacksnippets.net/example6.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[7]/a",
        "url": "https://stacksnippets.net/example7.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[8]/a",
        "url": "https://stacksnippets.net/example8.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[9]/a",
        "url": "https://stacksnippets.net/example9.com"
      },
      {
        "link": "//*[@id=\"target\"]/ul/li[10]/a",
        "url": "https://stacksnippets.net/example10.com"
      }
    ];
    
    abtArr.forEach(item => {
        
        const aTag = getElementByXpath(item.link);
        console.log(aTag);
        aTag.target = "_blank";
        aTag.click();
    
      	//Somehow verify that the new tab opened contains the pre-defined url:
        //.verify.urlContains(item.url)
    })
    <div id="target">
      <ul>
        <li><a href="example.com/1">Link</a></li>
        <li><a href="example.com/2">Link</a></li>
        <li><a href="example.com/3">Link</a></li>
        <li><a href="example.com/4">Link</a></li>
        <li><a href="example.com/5">Link</a></li>
        <li><a href="example.com/6">Link</a></li>
        <li><a href="example.com/7">Link</a></li>
        <li><a href="example.com/8">Link</a></li>
        <li><a href="example.com/9">Link</a></li>
        <li><a href="example.com/10">Link</a></li>
      </ul>
    </div>