I'm trying to get a property value of web element, setting property name dynamically.
Each time, when I use square brackets property accessor (Ex: node[propertyName]
), I get an error "elementHandle.evaluate: ReferenceError: propertyName is not defined".
But if I try the same property, using dot property accessor, it works fine and I get expected value.
The documentation of class JSHandle
and method evaluate
is here.
I tried to reproduce the same steps in Browser console.
const elements = $$("thead th");
const element = elements[0];
propertyName = "innerText";
const propertyValue2 = element.innerText;
const propertyValue = element[propertyName];
console.log(propertyValue2); // Expected property value
console.log(propertyValue); // Expected property value
But in my IDE, a got a little bit other results:
protected async getElementProperty(): Promise<string> {
const elements = await this.page.$$("thead th");
const element: JSHandle = elements[0];
propertyName = "innerText";
const propertyValue1: string = await element.evaluate((node) => node.innerText); // Expected property value
const propertyValue2: string = await element.evaluate((node) => node[propertyName]); // ReferenceError: propertyName is not defined
const propertyValue3: string = await element.evaluate(`(node) => node[${propertyName}]`); // Undefined
return propertyValue;
}
Playwright's evaluate
method is tricky: It converts the function you give it to source code, then rebuilds the function within the page environment. So your function can't close over variables, because they don't exist in the page environment, only in your script's environment.
You can see this in the "right" and "wrong" examples in the documentation:
Right:
const data = { text: 'some data', value: 1 }; // Pass |data| as a parameter. const result = await page.evaluate(data => { window.myApp.use(data); }, data);
Wrong:
const data = { text: 'some data', value: 1 }; const result = await page.evaluate(() => { // There is no |data| in the web page. window.myApp.use(data); });
Instead, pass propertyName
as a parameter as they've done in that "right" example, although your code is closer to this example further up the page:
await button.evaluate((button, from) => button.textContent.substring(from), 5);
More in the JSHandle.evaluate
docs.
So:
const propertyValue2: string = await element.evaluate(
(node, propertyName) => node[propertyName],
propertyName
);
Notice that the function receives a second parameter, and we pass a second argument to evaluate
.