Accessing object properties in foreach loop

Hello. Consider the below automation that I have created:

{
  "Name": "po",
  "CreationDate": "2026-8-6",
  "Commands": [
    {
      "Command": "open",
      "Target": "https://techygeekshome.info/freeware/",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "waitForElementPresent",
      "Target": "linkText=View Download ⬇",
      "Value": "",
      "Targets": [
        "linkText=View Download ⬇",
        "xpath=//*[@id=\"tgh-grid\"]/div/a",
        "xpath=//div[2]/div/a",
        "css=#tgh-grid > div:nth-child(1) > a"
      ],
      "Description": ""
    },
    {
      "Command": "executeScript",
      "Target": "var stuff = Array.from(document.getElementsByClassName(\"tgh-dl\"));\nreturn stuff;",
      "Value": "ele",
      "Description": ""
    },
    {
      "Command": "echo",
      "Target": "${ele.length}",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "forEach",
      "Target": "ele",
      "Value": "thing",
      "Description": ""
    },
    {
      "Command": "echo",
      "Target": "${thing.href}",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "end",
      "Target": "",
      "Value": "",
      "Description": ""
    }
  ]
}

As you can see, the automation simply opens a web page, then runs a bit of javascript to return a htmlcollection as an array object to the tool. I then want to take that returned array object and, using a foreach loop, echo the “href” property for each object stored in the array. I know that each object in the array has an href property, as I ran the same javascript code in the google dev tools console and looked at the array properties in the console. How do I accomplish what I am trying to do. I thought that the way that I have it set up now would accomplish this, but the echo commands just return “undefined.” What am I doing wrong.

{
      "Command": "executeScript",
      "Target": "return Array.from(document.getElementsByClassName(\"tgh-dl\"))\n  .map(el => el.href);",
      "Value": "ele",
      "Description": ""
    },
    {
      "Command": "echo",
      "Target": "${ele.length}",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "forEach",
      "Target": "ele",
      "Value": "thing",
      "Description": ""
    },
    {
      "Command": "echo",
      "Target": "${thing}",
      "Value": "",
      "Description": ""
    },
    {
      "Command": "end",
      "Target": "",
      "Value": "",
      "Description": ""
    }

Hi Matthew,

the problem is what executeScript can hand back to the macro. The script runs in the web page, but the returned value has to be serialized (JSON) to get back into Ui.Vision. DOM elements can’t be serialized — they’re live browser objects full of circular references and native handles — so each element in your array comes back as an empty object {}. That’s why ${ele.length} works (the array itself survives) but ${thing.href} is undefined (the elements’ properties don’t).

The fix: extract the plain data you need inside the script, and return that instead of the elements themselves (as @uiuser sugested!):

{
  "Command": "executeScript",
  "Target": "return Array.from(document.getElementsByClassName(\"tgh-dl\")).map(function(e) { return e.href; });",
  "Value": "ele"
}

Now ele is a plain array of strings, and your loop becomes:

{ "Command": "forEach", "Target": "ele",     "Value": "thing" },
{ "Command": "echo",    "Target": "${thing}", "Value": "" },
{ "Command": "end",     "Target": "",         "Value": "" }

:backhand_index_pointing_right: Rule of thumb: Anything you return from executeScript must survive JSON.stringify → strings, numbers, booleans, and plain arrays/objects are fine; DOM nodes, functions, and the like are not.

By the way, Ui.Vision V10 now also supports writing macros directly in JavaScript, and this kind of task is a natural fit! The whole macro above becomes this:

uiv.open('https://techygeekshome.info/freeware/');

//  (uiv.eval is the new executeScript, differnet name, same features)

const hrefs = uiv.eval(
  'return Array.from(document.getElementsByClassName("tgh-dl")).map(e => e.href)'
);

uiv.log(hrefs.length + ' links found');
for (const href of hrefs) {
  uiv.log(href);
}

No forEach/end commands, no ${…} variable juggling are needed anymore. You get real arrays and real loops. But note that the same serialization rule still applies inside uiv.eval (return plain data, not DOM elements).

JS macro in action:

THank you for that detailed explanation. This is exactly the information I was looking for. Thanks for taking the time.