How to use puppeteer and xflow to find and click text using variable - puppeteer

I am trying to setup automation to sign into my school. they add a discussion that i have to click on and type "here", in the name of the discussion they call it:"Attendance Month/Date" so i used
let current_datetime = new Date();
let formatted_date = (current_datetime.getMonth() + 1) + "/" + current_datetime.getDate();
which outputs the date "10/26" which i am trying to use to search for it in the page. I then decided to use const attendance = await page.$x("//a[contains(., Formatted_date)]"); await attendance[0].click(); which i found from someones answer on here already. When i try to use it the console will give me (node:15236) UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement but if i use const attendance = await page.$x("//a[contains(., '10/26')]"); await attendance[0].click(); it works fine, is there a way to fix/workaround it?

I figured it out i forgot to put +""+ around the variable

Related

Why is puppeteer $(<selector>) not working?

I am using pupteer to select a field input with the name of website. When i select it and then use the click method i keep getting an error message that says "website_input.click is not a function "
But when i try to do this
page.type("input[name='website']","test");
it works fine which i find very weird. Below is the code i am using. Any help would be really appreciated
const website_input = page.$("input[name='website']");
await website_input.click({clickCount: 3});
await website_input.press('Backspace');
The problem might be related to promises. You should await it and then work with the element:
const website_input = await page.$("input[name='website']");
await website_input.click({clickCount: 3});
await website_input.press('Backspace');
That's because page.$() method returns Promise<ElementHandle<T> | null>, not just element handle. You can check the docs.

Unable to locate an element with puppeteer

I'm trying to do a basic search on FB marketplace with puppeteer(and it was working for me before) but fails recently.
The whole thing fails when it gets to "location" link on marketplace page. to change the location i need to click on it, but puppeteer Errors out saying:
Error: Node is either not visible or not an HTMLElement
If i try to get the boundingBox of the element it returns null
const browser = await puppeteer.launch();
const page = await browser.newPage();
const resp = await page.goto('https://www.facebook.com/marketplace', { waitUntil: 'networkidle2' })
const withinLink = await page.waitForXPath('//span[contains(.,"Within")]', { timeout: 4000 })
console.log(await withinLink.boundingBox()) //returns null
await withinLink.click() //errors out
If i take a screenshot of the page right before i locate an element it is clearly there and i am able to locate in in chrome console using the same xPath manually.
It just doesn't seem to work in puppeteer
Something clearly changed on FB. Maybe they started to use some AI technology to detect scraping?
I don't think facebook changed in headless browser detection lately, but it seems you haven't taken into account that const withinLink = await page.waitForXPath('//span[contains(.,"Within")]', { timeout: 4000 }) returns an array, even if there is only one matching elment to contains(.,"Within").
That should work if you add [0] index to the elementHandles:
const withinLink = await page.waitForXPath('//span[contains(.,"Within")]')
console.log(await withinLink[0].boundingBox())
await withinLink[0].click()
Note: Timeout is not mandatory in waitForXPath, but I'd suggest to rather use domcontentloaded instead of networkidle2 in page.goto if you don't need all analytics/tracking events to achive the desired results, it just slows down your script execution.
Note 2: Honestly, I don't have such element on my fb platform, maybe it is market dependent. But it works with any other XPath selectors with specific content.

System.IO.DirectoryNotFoundException when calling SerialDevice.FromIdAsync

I'm making a simple helper class for accessing my barcode scanner. I can do it using SerialPort constructor like:
new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
But I want my class to autodetect propper port name, so I first find the DeviceInformation:
var all = await DeviceInformation.FindAllAsync();
var foundBarcode = all.First(device => device.Name.ToLower().Contains("barcode"));
Then I try to get It like this:
var serialDevice = await SerialDevice.FromIdAsync(foundBarcode.Id);
But when I do so I get 'System.IO.DirectoryNotFoundException' in System.Private.CoreLib.dll.
How can I fix it?
PS.
I have serial communication capability in my manifest.
I found a solution, appearently
DeviceInformation.FindAllAsync();
returns items with different ids than
string selector = SerialDevice.GetDeviceSelector();
var all = await DeviceInformation.FindAllAsync(selector);
Now it works, I guess when first approach adds some USB identificator for emulated COMs.

How do you paste text using Puppeteer?

I am trying to write a test (using jest-puppeteer) for an input in my React application that handles autocomplete or copy/pasted strings in a unique way.
I was hoping by using Puppeteer, I could paste text into the input and then validate that the page is updated correctly. Unfortunately, I can't find any working example of how to do this.
I've tried using page.keyboard to simulate CMD+C & CMD+V but it does not appear that these sorts of commands work in Puppeteer.
I've also tried using a library such as clipboardy to write and read to the OS clipboard. While clipboardy does work for write (copy), it seems read (paste) does not affect the page run by Puppeteer.
I have successfully copied the text using a variety of methods but have no way to paste into the input. I've validated this assumption by adding event listeners for "copy" and "paste" to the document. The "copy" events fire, but no method has resulted in the "paste" event firing.
Here are a few approaches I have tried:
await clipboardy.write('1234'); // writes "1234" to clipboard
await page.focus("input");
await clipboardy.read(); // Supposedly pastes from clipboard
// assert input has updated
await clipboardy.write('1234');
await page.focus("input");
await page.keyboard.down('Meta');
await page.keyboard.press('KeyV');
await page.keyboard.up('Meta');
// assert input has updated
await page.evaluate(() => {
const input = document.createElement('input');
document.body.appendChild(input);
input.value = '1234';
input.focus();
input.select();
document.execCommand('copy');
document.body.removeChild(input);
});
wait page.focus("input");
await page.keyboard.down('Meta');
await page.keyboard.press('KeyV');
await page.keyboard.up('Meta');
I think the only missing piece here is pasting the text; but how do you paste text using Puppeteer?
This works for me with clipboardy, but not when I launch it in headless :
await clipboardy.write('foo')
const input= await puppeteerPage.$(inputSelector)
await input.focus()
await puppeteerPage.keyboard.down('Control')
await puppeteerPage.keyboard.press('V')
await puppeteerPage.keyboard.up('Control')
If you make it works in headless tell me.
I tried it the clipBoard API too but I couldn t make it compile:
const browser = await getBrowser()
const context = browser.defaultBrowserContext();
// set clipBoard API permissions
context.clearPermissionOverrides()
context.overridePermissions(config.APPLICATION_URL, ['clipboard-write'])
puppeteerPage = await browser.newPage()
await puppeteerPage.evaluate((textToCopy) =>{
navigator.clipboard.writeText(textToCopy)
}, 'bar')
const input= await puppeteerPage.$(inputSelector)
await input.focus()
await puppeteerPage.evaluate(() =>{
navigator.clipboard.readText()
})
I came up with a funny workaround how to paste a long text into React component in a way that the change would be registered by the component and it would not take insanely long time to type as it normally does with type command:
For text copying I use approach from Puppeteer docs (assume I want to select text from first 2 paragraphs on a page for example). I assume you already know how to set the permissions for clipboard reading and writing (for example one of the answers above shows how to do it).
const fromJSHandle = await page.evaluateHandle(() =>Array.from(document.querySelectorAll('p'))[0])
const toJSHandle = await page.evaluateHandle(() =>Array.from(document.querySelectorAll('p'))[1])
// from puppeteer docs
await page.evaluate((from, to) => {
const selection = from.getRootNode().getSelection();
const range = document.createRange();
range.setStartBefore(from);
range.setEndAfter(to);
selection.removeAllRanges();
selection.addRange(range);
}, fromJSHandle, toJSHandle);
await page.bringToFront();
await page.evaluate(() => {
document.execCommand('copy') // Copy the selected content to the clipboard
return navigator.clipboard.readText() // Obtain the content of the clipboard as a string
})
This approach does not work for pasting (on Mac at least): document.execCommand('paste')
So for pasting I use this:
await page.$eval('#myInput', (el, value) =>{ el.value = value }, myLongText)
await page.type(`#myInput`,' ') // this assumes your app trims the input value in the end so the whitespace doesn't bother you
Without the last typing step (the white space) React does not register change/input event. So after submitting the form (of which the input is part of for example) the input value would still be "".
This is where typing the whitespace comes in - it triggers the change event and we can submit the form.
It seems that one needs to develop quite a bit of ingenuity with Puppeteer to figure out how to work around all the limitations and maintaining some level of developer comfort at the same time.

Access the query string in VueJS

I've just started using VueJS and I'm really liking it! :) I would like to save the values in the querystring to a VueJS variable - this is something super simple in handlebars + express, but seems more difficult in Vue.
Essentially I am looking for something similar to -
http://localhost:8080/?url=http%3A%2F%2Fwww.fake.co.uk&device=all
const app = new Vue({
...
data: {
url: req.body.url,
device: req.body.device
}
...
});
Google seemed to point me to vue-router, but I'm not sure if that's really what I need/how to use it. I'm currently using express to handle my backend logic/routes.
Thanks,
Ollie
You can either to put all your parameters in hash of the url, e.g.:
window.location.hash='your data here you will have to parse to'
and it will change your url - the part after #
Or if you insist to put them as query parameters (what's going after ?) using one of the solutions from Change URL parameters
You can use URLSearchParams and this polyfill to ensure that it will work on most web browsers.
// Assuming "?post=1234&action=edit"
var urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.has('post')); // true
console.log(urlParams.get('action')); // "edit"
console.log(urlParams.getAll('action')); // ["edit"]
console.log(urlParams.toString()); // "?post=1234&action=edit"
console.log(urlParams.append('active', '1')); // "?post=1234&action=edit&active=1"
Source:
https://davidwalsh.name/query-string-javascript
URLSearchParams
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
https://github.com/WebReflection/url-search-params/blob/master/build/url-search-params.js
See also:
https://stackoverflow.com/a/12151322/194717