What's the expected behavior of puppeteer's ElementHandle.getProperty()? - node.js

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"

Related

Cannot set input field value with Element.value [duplicate]

My extension has a context menu with items. What I'd like it to do: is when I right-click an editable html element (eg input or textarea) and then select and click on an item in my menu - some value defined by my extension gets entered into the input.
For now I have realised that with document.activeElement.value = myValue.
With simple inputs it works alright.
Problems start when there is an input with custom onChange event handling, eg a calendar or a phone input, or currency input - that transforms user-input in some way.
Since I am setting a value directly onto the element - the handling logic gets omitted, which causes all manner of problems.
Since javascript doesn't allow for KeySend-like features - what are my options here?
I have thought about testing tools like Puppeteer or Cypress - but they all seem not to be packageable into an extension. Puppeteer does have such an option, but it still requires a node instance running to connect to. And I would like my extension to be solely client-sided and distributed in Chrome webstore - so I cannot ask my users to spin up a node server.
There is a built-in DOM method document.execCommand.
In case of an extension, use this code in the content script.
// some.selector may be `input` or `[contenteditable]` for richly formatted inputs
const el = document.querySelector('some.selector');
el.focus();
document.execCommand('insertText', false, 'new text');
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
It imitates physical user input into the currently focused DOM element so all the necessary events will be fired (like beforeinput, input) with isTrusted field set to true. On some pages the change event should be additionally dispatched as shown above.
You may want to select the current text to replace it entirely instead of appending:
replaceValue('some.selector', 'new text');
function replaceValue(selector, value) {
const el = document.querySelector(selector);
if (el) {
el.focus();
el.select();
if (!document.execCommand('insertText', false, value)) {
// Fallback for Firefox: just replace the value
el.value = 'new text';
}
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
}
return el;
}
Note that despite execCommand being marked as obsolete in 2020, it'll work in the foreseeable future because a new editing API specification is not finished yet, and knowing how slow such things usually move it may take another 5-20 years.
#wOxxOm, thank you very much !
I used your code solved my problem which has bothered me for long time. I googled many code and article for nearly one month.
It works on Facebook and many strong website.
Because execCommand has depredated, I try below code it works well, include Facebook.
function imitateKeyInput(el, keyChar) {
if (el) {
const keyboardEventInit = {bubbles:false, cancelable:false, composed:false, key:'', code:'', location:0};
el.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
el.value = keyChar;
el.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
el.dispatchEvent(new Event('change', {bubbles: true})); // usually not needed
} else {
console.log("el is null");
}
}
The following code can only work on ordinary websites, but it is invalid for strong website.
function fireKeyEvent(el, evtType, keyChar) {
el.addEventListener(evtType, function(e) {el.value += e.key;}, false);
el.focus();
const keyboardEventInit = {bubbles:false, cancelable:false, composed:false, key:keyChar, code:'', location:0};
var evtObj = new KeyboardEvent(evtType, keyboardEventInit);
el.dispatchEvent(evtObj);
}

Playwright test library - parent element for selector

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

How to get updated html after changing page element values using puppeteer?

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');

nightmare.js trying to click on link based on anchor text

I'm trying to use nightmare, in node js to click on links based on the text inside the anchor text of the link.
Here's some example code:
var Nightmare = require('nightmare');
var nightmare = Nightmare({show: true})
nightmare
.goto('https://www.wikipedia.org/')
.inject('js', 'C:/users/myname/desktop/nodejs/node_modules/jquery/dist/jquery.js')
.wait(500)
var selector = 'a';
nightmare
.evaluate(function (selector) {
// now we're executing inside the browser scope.
return document.querySelector(selector).innerText;
}, selector) // <-- that's how you pass parameters from Node scope to browser scope
.end()
.then(function(result) {
console.log(result)
})
I'm really unclear on why the inner text of all tags are not returning? I thought I could maybe do an if statement in the .evalution method, so that it would restrict the link to be clicked on to "English" for instance.
Any idea how to click on links based on the link text?
As far as I know, there is no way to select a DOM element solely on what it contains. You'll either need to select all of the anchors (like you're doing now) and filter to what you want based on innerText then issue click events directly, or you could inject jQuery and use :contains and $.click() to issue the click.
Also, if you want all of the text from the tags, you'll likely want to use document.querySelectorAll().
As an example to get all of the text:
.evaluate(function (selector) {
return document.querySelectorAll(selector)
.map(element => element.innerText);
}, selector)

In NodeJS selenium-webdriver, how do you get innerHTML of element

I've gone through: https://code.google.com/p/selenium/wiki/WebDriverJs and it doesn't have any information. So, can someone help.
I have
var element = driver.findElements(webdriver.By.id("something"))
console.log('text='+element.getAttribute("inner‌​HTML"));
But doesn't work. Most of the documentation appears to be for JAVA not nodeJS. If you come across a .getText() function, I'm pretty sure that is JAVA. I actually just want the text part innerText, opposed to innerHTML. But that might be asking too much.
You can check innerHTML like this:
driver.executeScript(function() {
return document.querySelector('#something').innerHTML;
}).then(function(innerHTML) {
//check content here
});
Per the webdriver.js docs:
var myElement = element(by.css('.myclass'));
myElement.getInnerHtml().then(function(html) {
//do stuff with html here
});
Hope that helps! It's working for me using Node.js + Selenium / WebDriver, etc.

Resources