Why does Puppeteer not save new textarea value? - javascript

This is a Puppeteer script running on Apify, designed to enter values into a form and save it. The fields are an input and a textarea, respectively.
The problem is - while the values of both fields are getting successfully updated in the front end (evidenced by taking screenshots), the value of the textarea is not actually saved when the Save <button> is .clicked, even though the input value alone is saved.
Why is this happening, and how do I overcome it?
I have tried several different methods of entering the textarea text:
Unlike the input, I have opted against using .type for the textarea (even though it worked fine for small amounts of text) due to the volume of the text being entered - it was causing timeouts.
I have tried using wait times, artificially changing focus and clicking into the textarea followed by .typeing an arbitrary value. In no circumstance does the textarea value get saved.
I have read on Stackoverflow that textareas should be edited via .innerText rather than .value, but this seems to make no difference in my case.
FYI - the form only becomes visible and active when the user/script clicks an Edit button (before this, the page had showed the form fields as static content). After the single Edit is clicked, two Save buttons appear (.edit-video-info becomes .save-video-info At the top, and then another .save-video-info appears. But a) the script simply uses the first one found and b) the Save operation is otherwise successful, as borne out by the input value getting saved.
import { Actor } from 'apify';
import { PuppeteerCrawler } from 'crawlee'; // "puppeteer" must be included in package.json dependencies
await Actor.init();
console.log('Actor initialised.')
console.log('Welcome. Only run if input was passed...')
const input = await Actor.getInput();
if (input) {
console.log('Input was passed...')
console.log(input);
// Create an instance of the PuppeteerCrawler class - a crawler
// that automatically loads the URLs in headless Chrome / Puppeteer.
const crawler = new PuppeteerCrawler({
// Here you can set options that are passed to the launchPuppeteer() function.
launchContext: {
launchOptions: {
headless: true,
// Other Puppeteer options
defaultViewport: { width: 1800, height: 2100 }
},
},
requestHandlerTimeoutSecs: 180,
// Stop crawling after several pages
maxRequestsPerCrawl: 50,
async requestHandler({ request, page, enqueueLinks }) {
/************************************
* Function: Long-text workaround - used for body
************************************/
// workaround cf. https://github.com/puppeteer/puppeteer/issues/4192#issuecomment-475241477
async function setSelectVal(sel, val) {
page.evaluate((data) => {
return document.querySelector(data.sel).value = data.val
}, { sel, val })
}
console.log(`Processing ${request.url}`);
/************************************
* Authentication
************************************/
let isLoggedIn = false;
// Is site logged-in? true/false
const loggedCheck = async (page) => {
try {
await page.waitForSelector('input#user', { timeout: 10000 });
console.log('Logged-in: true.');
return true;
}
catch(err) {
console.log('Logged-in: false.');
return false;
}
};
isLoggedIn = await loggedCheck(page);
if (!isLoggedIn) {
console.log('Need to do login...');
const SECRET_EMAIL = 'MYEMAILADDRESS'
const SECRET_PASSWORD = 'MYPASSWORD'
await page.waitForSelector('#signinButton')
console.log('Entering email...');
await page.type('input#email', SECRET_EMAIL)
console.log('Entering password...');
await page.type('input#password', SECRET_PASSWORD)
console.log('Clicking #signinButton...');
await page.click('#signinButton')
console.log('Clicked #signinButton.');
isLoggedIn = await loggedCheck(page);
console.log('Login should now be true.');
console.log(isLoggedIn);
}
/************************************
* Kicking off...
************************************/
console.log('Do stuff when #video-details loads...');
await page.waitForSelector('#video-details', { timeout: 30000 })
// Increase browser size
await page.setViewport({
width: 1200,
height: 2400,
deviceScaleFactor: 1,
});
console.log('Existing video title...');
console.log(await page.evaluate(() => document.evaluate('normalize-space(//div[#data-test-name="video-name"]/div[2])', document, null, XPathResult.STRING_TYPE, null).stringValue));
console.log('Echoed.');
/************************************
* Edit video details...
************************************/
console.log('Clicking Edit button...');
await page.click('button[data-test-name=edit-video-info]')
console.log('Clicked Edit button.');
/************************************
* 1. Video title
************************************/
console.log('Wait for input field...');
await page.waitForSelector('label[data-test-name=video-name-input]', { timeout: 30000 })
console.log('OK.');
console.log('Changing video title...')
if (input.title) {
console.log('input.title is available')
const titlebox = await page.$('label[data-test-name=video-name-input] input');
await titlebox.click({ clickCount: 3 })
await titlebox.type(input.title);
console.log('OK')
} else {
console.log('No input.title - will not change.')
}
/************************************
* 2. Body text
************************************/
console.log('Wait for body textarea...');
await page.waitForSelector('label[data-test-name=long-description-textarea] textarea', { timeout: 30000 })
console.log('OK.');
console.log('Changing body text...')
if (input.body) {
console.log('input.body is available')
const bodytext = input.body;
console.log(bodytext);
await setSelectVal('label[data-test-name=long-description-textarea] textarea', bodytext)
console.log('OK')
} else {
console.log('No input.body - will not change.')
}
/* Screenshot */
const screen07typedinput = await page.screenshot();
await Actor.setValue('screen07typedinput.png', screen07typedinput, { contentType: 'image/png' });
/************************************
* Save video details
************************************/
// await page.focus('button[data-test-name=save-video-info]')[1];
await page.waitForTimeout(3000)
console.log('Clicking Save button...');
await page.click('button[data-test-name=save-video-info]')
console.log('Did stuff');
/* Screenshot */
const screen08aftersave = await page.screenshot();
await Actor.setValue('screen08aftersave.png', screen08aftersave, { contentType: 'image/png' });
/************************************
* Logout
************************************/
console.log('Trying logout...')
await page.goto('https://signin.example.com/logout');
console.log('Finished.');
},
// This function is called if the page processing failed more than maxRequestRetries+1 times.
failedRequestHandler({ request }) {
console.log(`Request ${request.url} failed too many times.`);
},
});
// Run the crawler and wait for it to finish.
// await crawler.run(['https://news.ycombinator.com/']);
await crawler.run(['https://site.example.com/products/videocloud/media/videos/'+input.assetid]);
console.log('Crawler finished.');
} else {
console.log('No input. Abandon.')
}
await Actor.exit();
The button markup is...
<button class="Button-blueSteel-4_1_4-3Skod Button-btn-4_1_4-QuPwe Button-primary-4_1_4-2YCmt Button-small-4_1_4-1Wurh" data-test-name="save-video-info" role="button" tabindex="0" type="button" aria-pressed="true">
<span class>Save</span>
</button>
An id attribute is absent, so I can't use getElementByID.

This works...
I had not .clicked inside the <textarea>.
Whilst I had .clicked the <input> field prior to .typeing into it, I had not done this with the <textarea> - because I wasn't using .type there; I was having to set the `.values.
In addition to that, for good measure, I .click Back in to the <input> field.
Also, I `.types an arbitrary value.
I think it was the clicks. Without having clicked the <textarea>, only the apparent .value was getting set.
await page.click('label[data-test-name=long-description-textarea] textarea')
await setSelectVal('label[data-test-name=long-description-textarea] textarea', bodytext)
await page.click('label[data-test-name=long-description-textarea] textarea')
await page.type('label[data-test-name=long-description-textarea] textarea',' ')
await page.click('label[data-test-name=video-name-input] input')

Related

Axois error on doing work with the dbd and nodejs

I am making a chat application that is setting an avatar image and when i set a avatar image then in the user that is already register and persent in my db i.e. mongo
And am setting the image and here is the code of my function that will set the avatar image in my db
`
const setProfilePicture = async () =>{
if(selectedAvatar === undefined){
toast.error("Please select an Avatar", toastOptions)
}else{
const user = await JSON.parse(localStorage.getItem("chat-app-user"));
const {data} = await axios.post(`${setAvatarRoute}/${user._id}`,{
image:avatars[selectedAvatar],
});
if(data.isSet){
user.isAvatarImageSet = true;
user.avatarImage = data.image;
localStorage.setItem("chat-app-user", JSON.stringify(user));
navigate("/")
}else{
toast.error("Error setting avatar. Please try again", toastOptions)
}
}
}
`and here my console that raising the error on clicking the button
Button that has onClick event enter image description here

html file button not clicking

i have this code that checks if the user has logged in or not, if the user has not logged in, it redirects the user to the login page to log in and if he/she has logged in, they would be allowed to upload content on the page. this is the code:
let vocals = document.getElementsByClassName("up");// class name of certain divs in the program
for (let i = 0; i < vocals.length; i++) {
vocals[i].addEventListener("click", () => {
let check = "php/checkCookie.php";
fetch(check, { method: "GET" })
.then((res) => res.text())
.then((data) => {
if (data == 0) {
shout("Login Before You Can Create", 0);
setInterval(() => {
location.href = "logincheck.html";
}, 3000);
} else {
document.getElementById(`e${i}`).click();
}
});
...
});
}
the code:
document.getElementById(`e${i}`).click();
is supposed to open the file explorer so that the user can pick a file and upload, but it doesn't work, and it does not show an error.
those anyone know why and has a solution, thanks in advance
Maybe your element doesn't support click() on it? If I remember correctly, click() works on on several elements, but not all. It should work on <input>, and <a>.
You could also try this instead:
document.getElementById(`e${i}`).dispatchEvent(new MouseEvent('mousedown'))
document.getElementById(`e${i}`).dispatchEvent(new MouseEvent('mouseup'))

javascript autoclick does not seem to autoclick

I'm really new to javascript and jquery and I'm trying to get a button to autoclick from an existing code base. I am having trouble getting the button to do that even though this is what I've been following How to auto click an input button.
The code is below. I've tried using both .submit() and .click() but the start-game-btn doesn't seem to get pressed either way. I've put a console.log within the button whenever it's pressed and the message doesn't seem to get logged.
console.log("loading...")
$(() => {
$("#start-game-btn").click(event => {
console.log("start-game")
$("#errors").text("")
event.preventDefault()
const height = parseInt($("#height").val())
const width = parseInt($("#width").val())
const food = parseInt($("#food").val())
let MaxTurnsToNextFoodSpawn = 0
if ($("#food-spawn-chance").val()) {
MaxTurnsToNextFoodSpawn = parseInt($("#food-spawn-chance").val())
}
const snakes = []
$(".snake-group").each(function() {
const url = "http://0.0.0.0:8080/"//$(".snake-url", $(this)).val()
console.log(url)
if (!url) {
return
}
snakes.push({
name: "M1",//$(".snake-name", $(this)).val(),
url
})
})
if (snakes.length === 0) {
$("#errors").text("No snakes available")
}
console.log(12)
fetch("http://localhost:3005/games", {
method: "POST",
body: JSON.stringify({
width,
height,
food,
MaxTurnsToNextFoodSpawn,
"snakes": snakes,
})
}).then(resp => resp.json())
.then(json => {
const id = json.ID
fetch(`http://localhost:3005/games/${id}/start`, {
method: "POST"
}).then(_ => {
$("#board").attr("src", `http://localhost:3009?engine=http://localhost:3005&game=${id}`)
}).catch(err => $("#errors").text(err))
})
.catch(err => $("#errors").text(err))
})
console.log("ready!")
})
window.onload = function(){
var button = document.getElementById('start-game-btn');
button.form.submit();
console.log("Done!!")
}
Everytime I refresh the page the log shows:
loading...
ready!
Done!!
What I think it should be logging is (or at least that's what I'm trying to achieve):
loading...
start-game
ready!
Done!!
submit() does not work because it is for submitting forms.
button.form.submit(); does not work because it assumes your button is part of a form.
As it looks like you are using jQuery, try just calling the jQuery click function without any parameters using a jQuery selector.
eg.
$("#start-game-btn").click();

Selenium Get Element After Following Link

all!
I've followed as many tips and tutorials as I can, but I can't figure out how to obtain an element in Node JS Selenium after moving from the starting page.
In my use case, I go to a login screen, enter the pertinent information, and then run a click() on the login button. I am taken to the home screen.
After this, I cannot select any elements! I just keep getting 'not found' warnings.
I'm sleeping for 6000ms, and I have set the implicit timeouts to 30s.
What am I missing here?
My problematic part would be in the function GoToSettings, which is called after the Login button is clicked.
Thanks
var webdriver = require('selenium-webdriver'),
By = webdriver.By,
until = webdriver.until;
const TIMEOUT = 30
var driver = new webdriver.Builder().forBrowser('chrome').build();
const capabilities = driver.getCapabilities().then(()=>{
capabilities['map_'].set('timeouts', { implicit: TIMEOUT, pageLoad: TIMEOUT, script: TIMEOUT });
});
var info = require('./info.json')
driver.get('http://www.okcupid.com/settings?')
driver.sleep(2000);
//We may already be logged in at this point
driver.getTitle().then((title) => {
driver.getCurrentUrl().then((url) => {
LoginOrHome(title, url);
});
});
function LoginOrHome(title, url){
if(title == "Free Online Dating | OkCupid" || url == "https://www.okcupid.com/login?p=/settings") {
//needslogin.exe
console.log("Logging in!");
login();
} else if (title == "Welcome! | OkCupid" || url == "https://www.okcupid.com/settings?") {
//We are already logged in
console.log("Already logged in. Changing location.");
goToSettings();
} else {
console.log(title);
console.log(url);
}
}
function goToSettings() {
driver.sleep(6000);
driver.switchTo().window(driver.getWindowHandle()).then( () => {
driver.getCurrentUrl().then((title) => {
//This prints the URL of the previous page
console.log(title);
});
})
}
function login(){
driver.findElement(By.name("username")).sendKeys(info.email).then(() => {
driver.findElement(By.name("password")).sendKeys(info.password).then(() => {
driver.findElement(By.css("div > input[type=submit]")).click().then(()=> { goToSettings() });
});
});
}

Casperjs Google Login (V2) Not working

I´m using casperjs (so phantomjs in the middle) to access some google utilities but, before accessing them we should be logged in google. For V1 google authentication, we are using the following script:
var casper = require('casper').create();
url = 'https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Faccounts.google.com%2FManageAccount&followup=https%3A%2F%2Faccounts.google.com%2FManageAccount&flowName=GlifWebSignIn&flowEntry=ServiceLogin&nojavascript=1#identifier';
casper.start(url, function() {
this.fillSelectors('form#gaia_loginform', {
'input[name="Email"]': 'your#email',
}); //Fills the email box with email
this.click("#next");
this.wait(500, function() { //Wait for next page to load
this.waitForSelector("#Passwd", //Wait for password box
function success() {
console.log("SUCCESS...");
this.fillSelectors('form#gaia_loginform', {
'input[name="Passwd"]': 'yourPassw',
}); //Fill password box with PASSWORD
this.click("#signIn"); //Click sign in button
this.wait(500, function() {}); //Wait for it fully sigin
casper.thenOpen('http://utility.google.com/', function() {
this.wait(2000, function() {
this.capture('media/status.png', undefined, {
format: 'png',
quality: 100`enter code here`
});
});
});
},
function fail() {
console.log("FAIL...");
}
);
});
});
casper.run();
We have changed the way that we manipulate the form and fill the fields and It's working so far. The problem with V2 authentication is that triggering the mouse events isn't possible, that means we can't click using this.click("#next") and this.click("#signIn"). I tried doing post over the form, using different mouse events and also trying to manipulate directly the jsaction events. Nothing works.
Someone has an idea on how to solve this issue? Thank you so much!
Casper use PhantomJS, and Phantom itself is unable to login in google account login. It seems to use any ES6 feature not supported in phantomjs it fails silently.
Maybe you can have more luck with beta phantomjs 2.5.
Anyway, phantomjs is deprecated in favor of chrome headless. As said by phantom maintainer Vitaly Slobodin
https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE
The good news is that you can start chrome in headless mode as: /opt/google/chrome/chrome --headless --disable-gpu --repl and do whatever you want.
You can replace the --repl with --remote-debugging-port=9224 to control it in any remote code, like a program in node...
There is libraries to control it like phantomjs.
High level (like phantom): https://github.com/GoogleChrome/puppeteer
and lower levels to have more control: https://github.com/cyrus-and/chrome-remote-interface#clientdomainmethodparams-callback
Currently I had no luck with puppeteer but with chrome-remote-interface I was able to login in google account.
const CDP = require('chrome-remote-interface');
const argv = require('minimist')(process.argv.slice(2));
const file = require('fs');
// CLI Args
const url = argv.url || 'https://accounts.google.com';
const format = argv.format === 'jpeg' ? 'jpeg' : 'png';
const viewportWidth = argv.viewportWidth || 1440;
const viewportHeight = argv.viewportHeight || 900;
let delay = argv.delay || 0;
const userAgent = argv.userAgent;
const fullPage = argv.full;
// Start the Chrome Debugging Protocol
CDP(async function(client) {
// Extract used DevTools domains.
const {DOM, Emulation, Network, Page, Runtime} = client;
// Enable events on domains we are interested in.
await Page.enable();
await DOM.enable();
await Network.enable();
// If user agent override was specified, pass to Network domain
if (userAgent) {
await Network.setUserAgentOverride({userAgent});
}
// Set up viewport resolution, etc.
const deviceMetrics = {
width: viewportWidth,
height: viewportHeight,
deviceScaleFactor: 0,
mobile: false,
fitWindow: false,
};
await Emulation.setDeviceMetricsOverride(deviceMetrics);
await Emulation.setVisibleSize({width: viewportWidth, height: viewportHeight});
// Navigate to target page
await Page.navigate({url});
// Wait for page load event to take screenshot
Page.loadEventFired(async () => {
// If the `full` CLI option was passed, we need to measure the height of
// the rendered page and use Emulation.setVisibleSize
if (fullPage) {
const {root: {nodeId: documentNodeId}} = await DOM.getDocument();
const {nodeId: bodyNodeId} = await DOM.querySelector({
selector: 'body',
nodeId: documentNodeId,
});
const {model: {height}} = await DOM.getBoxModel({nodeId: bodyNodeId});
await Emulation.setVisibleSize({width: viewportWidth, height: height});
// This forceViewport call ensures that content outside the viewport is
// rendered, otherwise it shows up as grey. Possibly a bug?
await Emulation.forceViewport({x: 0, y: 0, scale: 1});
}
let expr="document.querySelector('input[type=email]').value='YOUREMAIL#gmail.com';";
expr+="document.querySelectorAll('div[role=button]')[0].click();";
setTimeout
let x=await Runtime.evaluate({expression: expr});
console.log('******' + JSON.stringify(x));
setTimeout(async function(){
expr="document.querySelector('input[type=password]').value='YOUR_PASSWORD';";
expr+="document.querySelectorAll('div[role=button]')[1].click()";
x=await Runtime.evaluate({expression: expr});
console.log('******' + JSON.stringify(x));
x=await ( async function() {
let expr="document.querySelector('input[type=password]')";
return Runtime.evaluate({expression: expr});
})()
console.log('**' + JSON.stringify(x));
}, 2000);
delay=5000
setTimeout(async function() {
const screenshot = await Page.captureScreenshot({format});
const buffer = new Buffer(screenshot.data, 'base64');
file.writeFile('output.png', buffer, 'base64', function(err) {
if (err) {
console.error(err);
} else {
console.log('Screenshot saved');
}
client.close();
});
}, delay);
});
}).on('error', err => {
console.error('Cannot connect to browser:', err);
});
References:
https://medium.com/#dschnr/using-headless-chrome-as-an-automated-screenshot-tool-4b07dffba79a
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#browserwsendpoint
https://developers.google.com/web/updates/2017/04/headless-chrome
I also trying same and I found that the click is working with this.click('#identifierNext'); and Google loaders start working. if you use following code after click to take screenshots you can see loader comes up but after that instead of going to Password screen it comes back to email screen.
Screenshot code
this.wait(200, function(){
this.capture('1.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('2.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('3.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('4.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
But I am also not able to reach Password screen, if with this help you can come with any idea let me know.

Categories

Resources