I'm trying to use ElementHandle.Jeval() to get element style attribute through Pyppeteer. But the problem is I can only address the element by xpath since the element class is dynamically generated.
However, the ElementHandle.Jeval() requries selector as one of the parameters.
I've tried:
element = await iframe.Jx('//*[#id="root"]/main/div[1]/div/div[9]/div[1]/div[2]/div[2]/div/div[2]')
style = await element[0].Jeval(pageFunction='node => node.getAttribute("style")')
It still requires selector. And I tried to find a way to get selector from the ElementHandle I addressed through xpath, but didn't find out a method allow me to do so.
and:
xpath = '//*[#id="root"]/main/div[1]/div/div[9]/div[1]/div[2]/div[2]/div/div[2]'
style = await iframe.Jeval(xpath, pageFunction='node => node.getAttribute("style")')
-------------------------------
raise ElementHandleError('Evaluation failed: {}'.format(
pyppeteer.errors.ElementHandleError: Evaluation failed: DOMException: Failed to execute 'querySelector' on 'Document': '//*[#id="root"]/main/div[1]/div/div[9]/div[1]/div[2]/div[2]/div/div[2]' is not a valid selector.
at __pyppeteer_evaluation_script__:1:33
If anyone can figure this out, please let me know. Thanks.
-----------------update---------------
I figured out through editing the source code:
#!element_handle.py
# elementHandle = await self.querySelector(selector)
elementHandle = selector
if not elementHandle:
raise ElementHandleError(
f'Error: failed to find element matching selector "{selector}"'
)
result = await self.executionContext.evaluate(
pageFunction, elementHandle, *args)
await elementHandle.dispose()
return result
However, I don't want to do so. If anyone have a better way, let me know. Thanks.
Related
I am trying to wait for an element that indicates a page is still loading, and exists on the page multiple time, to not be visible (think table with loading data placeholders).
Playwright documentation suggests using Locators is best practice, and therefore I initially tried to achieve this by doing:
locator.waitFor({state: "hidden")
However that errors due to Locators being strict and only being allow to match one element.
I'm now doing it with the following code:
page.waitForSelector(".foo .bar", {state: "hidden"})
This is non-ideal for a couple of reasons:
I'm storing page elements as locators in the Page Object Model, and you seemingly cannot access the selector of a locator, meaning the selector is duplicated in the code
I believe page.waitForSelector will use an ElementHandle which is discouraged
Is there any way to turn off the strictness constraint on a Locator? Or a way to achieve this using the Locator. I'm aware you can do .count on a Locator which matches multiple elements, but I've not found a nice way to combine that with waiting for the count to be 0.
I got this working in the end using the evaluateAll method. Example code:
async waitForAllHidden(locator: Locator, timeout: number = 10000) {
const start = Date.now()
const elementsVisible = async () => (await locator.evaluateAll(elements => elements.map(element => element.hidden))).includes(false)
while (await elementsVisible()) {
if (start + timeout < Date.now()) {
throw(`Timeout waiting for all elements to be hidden. Locator: ${locator}. Timeout: ${timeout}ms`);
}
}
console.log(`All elements hidden: ${locator}`)
}
hope it will work
this code will check the next element each time the previous one disappears
while (await page.locator('.foo .bar').first().isVisible()) { //do nothing }
please, how to get a parent element for a text selector by the Playwright E2E library.
Is better to modify the selector (it is string by something like >> //:parent) or evaluate the selector and then call the DOM element?
(The selector content is unknown)
Thank you.
You can call .$ to start searching from the element:
const elem = await page.$(anySelector)
const parent = await elem.$('xpath=..')
Doc: https://playwright.dev/docs/api/class-elementhandle#elementhandleselector
Using the new Locator you can do:
const elementParent = await page.locator(`${childSelector} >> xpath=..`)
https://playwright.dev/docs/api/class-locator
https://playwright.dev/docs/selectors#xpath-selectors
I want to click on an element without using css selectors.
await page.click()
uses selectors to identify the element.
So how can I do something like this?
await page.click('/*[#id="toc"]/ul/li[1]/a')
First I had to get the element by using
await page.$x('<xPath>')
It returns an array with elements. To click I had to choose the first element in the array.
const elements = await page.$x('<xPath>')
await elements[0].click()
You can use the xpath prefix with puppeteer 19 and newer
await page.click('xpath/' + xpathExpression)
e.g.
await page.click('xpath//*[#id="toc"]/ul/li[1]/a')
Puppeteer 1.0.0-post. The getProperty() method seems somewhat magical. For example, if your page contains:
link
Then this will return not a relative but an absolute URL:
const propertyHandle = await elementHandle.getProperty('href');
const href = await propertyHandle.jsonValue();
// href is 'https://localhost:8080/foo/bar.html'
On the other hand, if you were to do the more roundabout:
const hrefHandle = await page.evaluateHandle(element => element.getAttribute('href'), elementHandle);
const href = await hrefHandle.jsonValue();
// href is '/foo/bar.html'
As far as I can tell, the puppeteer documentation doesn't mention this behavior of getProperty()?
It gets uglier, for instance if you want to get the style attribute of an element. It looks like puppeteer's getProperty() actually tries to parse the style in some way, which parsing is buggy/incomplete. The only way to get the raw text is with the roundabout call to evaluateHandle(...).
Is this an intended feature and simply a documentation bug? Or is it just, outright, a puppeteer bug?
Thanks.
See HTML - attributes vs properties for difference between HTML attributes and DOM properties.
You can easily see the difference without Puppeteer, too. For example, on this page:
document.getElementById('nav-questions').href
// returns "https://stackoverflow.com/questions"
document.getElementById('nav-questions').getAttribute('href')
// returns "/questions"
I'm at a loss. I'm typing into an input element on the page using this:
await page.type('#username', 'test');
Then I'd like to retrieve the newly updated, which I'm trying using this:
let html = await page.content();
But the username field does not include the word 'test' in it - it is empty. If I take a screenshot, it has the word 'test' in it. What am I missing?
Typing in an <input> element updates the value property but leaves the value attribute unchanged. This is why you don't see the text in the rendered HTML.
To get the value property:
await page.type('#username', 'test');
let value = await page.$('#username').getProperty('value');