Selecting dropdown option with puppeteer - puppeteer

Trying to automate selection of a dropdown item in the Wunderground/wundermap (https://www.wunderground.com/wundermap) and struggling a bit. The selection item isn't named, and gets a random ID every page load (common element to the ID, but new numbers). The element is:
<select aria-label="Map Types" class="header-select ng-pristine ng-valid ng-touched" style="width: 200px;" id="mapTypes0.3556425555390934"><option title="Show street map with terrain" value="terrain" selected="selected">Terrain</option><option title="Show Dark Map" value="darkmap">Dark Map</option><option title="Show Light Map" value="lightmap">Light Map</option><option title="Show satellite imagery" value="satellite">Satellite</option><option title="Show imagery with street names" value="hybrid">Hybrid</option></select>
Trying to select darkmap using puppeteer.
I've tried a couple of page eval options but they don't seem to be finding the element. Any suggestions?

This seems working:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false, defaultViewport: null });
try {
const [page] = await browser.pages();
await page.goto('https://www.wunderground.com/wundermap');
const select = await page.waitForSelector('select[aria-label="Map Types"]');
await select.select('darkmap');
} catch (err) { console.error(err); }

Related

How to create autocomplete box using vue.js?

I am new at Vue, now I am creating simple search app (using Vue.js cdn).
I want to append suggestion bar which contains all user id's from fake JSON server, for example if I write into search bar 1, I want to append only user which id is 1, and then I click to that user id I want to send another request to receive only this user info.
I am stuck, how I can solve this?
var app = new Vue({
el: '#app',
data: {
message: '',
searchKey:'',
result:[]
},
methods:{
async getData() {
// GET request using fetch with async/await
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${this.searchKey}`);
const data = await response.json()
this.result = data
},
},
created(){
this.getData()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.js"></script>
<div id="app">
<div class="search-area">
<div class="header-wrapper">
<h1>Tag Search</h1>
</div>
<div class="search-bar-custom">
<input placeholder="Search tags" v-model="searchKey" #keyup="getData" />
<div class="suggetions">
<ul class="suggestions" id="suggestions">
<li><h1>suggetion id</h1></li>
</ul>
</div>
</div>
</div>
</div>
You are on the right way, but, there are some issues about your logic here, for json-server you need to use the Operator _like (https://github.com/typicode/json-server#operators) to retrieve or filter data depending the column or property, so, your getData method must be like this:
async getData() {
// GET request using fetch with async/await
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?name_like=${this.searchKey}`
);
const data = await response.json();
this.result = data;
},
You can change the property or column, in example username_like or id_like.
Finally, you need to show the results, so, change your template:
<ul class="suggestions" id="suggestions">
<h1 v-for="item in result" #mousedown="show(item)">
{{ item.id }} | {{ item.name }}
</h1>
</ul>
Pay attention on #mousedown="show(item)", when user click on some result, this action will display the data about user, for that, we need to create a new method called show and pass the item:
show(item) {
alert(JSON.stringify(item, null, 2));
}
You can look how it works here: https://codepen.io/riateam/pen/ExNrGOE?editors=1010

React App crashes sometimes when opening react-signature-canvas

Sorry, I can't be very specific with the details of the problem as it only happens sometimes, and I haven't been able to recreate it, which means I have no clue where to start trying to fix it.
It appears to only happen on really cheap android tablets.
I have a page with a form where the user fills in details, The problem happens just after they have entered their name into a text field and then once they press onto the react-signature-canvas to start drawing their signature the app crashes (doesn't crash all the time).
in the past, I think the crash was caused when the keyboard was still open when the user tried to start drawing on the signature pad.
As I said, I'm finding it really difficult to fix as I can't recreate it, so any help at all would be greatly appreciated.
I'm using React Hooks and Formik.
Form:
<h2>Guardian Full Name</h2>
<MyTextField
label="Guardian Full Name"
name="parentName"
required
/>
<ErrorMessage
component={"div"}
className={"termsConditionText error"}
name={"parentSignature"}
/>
<SignaturePad setFieldValue={setFieldValue} />
SignaturePad:
import React, { useRef, useState } from "react";
import { Button } from "semantic-ui-react";
import "../../pages/SignDisclaimerForm/SignDisclaimerForm.css";
import "./signaturePad.css";
import SignatureCanvas from "react-signature-canvas";
export const SignaturePad = props => {
const [canvasImageUrl, setCanvasImageUrl] = useState([
props.parentSignature || ""
]);
let sigCanvas = useRef();
const clearCanvas = () => sigCanvas.current.clear();
const saveCanvas = async () => {
if (sigCanvas.current.isEmpty()) return;
document.getElementById("parentName").blur();
props.setFieldValue(
"parentSignature",
sigCanvas.current.getTrimmedCanvas().toDataURL("image/png")
);
setCanvasImageUrl(
sigCanvas.current.getTrimmedCanvas().toDataURL("image/png")
);
};
return (
<div>
{!props.disabled && (
<div>
<h2 style={{ marginLeft: "5%" }}>Guardian Signature</h2>
<div className={"sigContainer"}>
<SignatureCanvas
ref={sigCanvas}
canvasProps={{ className: "sigPad" }}
onEnd={saveCanvas}
/>
</div>
<Button
style={{ marginLeft: "5%", marginTop: "2%", marginRight: "2%" }}
type={"button"}
onClick={clearCanvas}
children={"Clear"}
/>
<br />
<br />
</div>
)}
{canvasImageUrl[0] && (
<div className={"signatureDisplay"}>
<img
src={canvasImageUrl}
alt={"Guardian Signature"}
style={{ height: "100%", width: "100%" }}
/>
</div>
)}
</div>
);
};
Sentry issue report also below.
Issue Title:
TypeError HTMLCanvasElement.r(src/helpers)
error
Cannot read property 'push' of undefined
Issue Body:
../../src/helpers.ts in HTMLCanvasElement.r at line 85:17
}
// Attempt to invoke user-land function
// NOTE: If you are a Sentry user, and you are seeing this stack frame, it
// means the sentry.javascript SDK caught an error invoking your application code. This
// is expected behavior and NOT indicative of a bug with sentry.javascript.
return fn.apply(this, wrappedArguments);
// tslint:enable:no-unsafe-any
} catch (ex) {
ignoreNextOnError();
withScope((scope: Scope) => {
Bread Crumbs:
This is what the form looks like:
Formik author here...
You might be setting state from an unmounted DOM element (the canvas). It doesn't happen all the time because it's a race condition. You should check whether the canvas ref is actually mounted before using methods on it within your callbacks.
// ...
const sigCanvas = useRef(null);
const clearCanvas = () => {
if (sigCanvas.current != null) {
sigCanvas.current.clear();
}
};
const saveCanvas = async () => {
// Ensure that the canvas is mounted before using it
if (sigCanvas.current != null) {
if (sigCanvas.current.isEmpty()) return;
document.getElementById("parentName").blur();
props.setFieldValue(
"parentSignature",
sigCanvas.current.getTrimmedCanvas().toDataURL("image/png")
);
setCanvasImageUrl(
sigCanvas.current.getTrimmedCanvas().toDataURL("image/png")
);
}
};
// ...
Thank you to everyone who helped me, I really appreciated it.
What I did in the end to fix the problem was just to have a green button the user had to press in order to open the signature pad.
The fact that the user has to press the open button, gives the keyboard enough time to completely dismiss before the user starts to draw on the signature pad.
Thank you :)

Clicking in input button not functioning as expected

I have the following code:
<li id="ecf43e6a-0a99-4b88-9927-7f6b681bc18d" class="checked">
<input type="button">
<span>Green</span>
</li>
I'm trying to click on the button by doing this.
const btnWrap = await page.$('#ecf43e6a-0a99-4b88-9927-7f6b681bc18d');
await btnWrap.$eval('input', el => el.click());
I'm getting an error on this any idea why it's not working?
For me your script works just fine.
You may need to wait for the element to be appeared:
await page.waitForSelector('#ecf43e6a-0a99-4b88-9927-7f6b681bc18d')
I think that will do the job as well:
await page.$eval('#ecf43e6a-0a99-4b88-9927-7f6b681bc18d > input', el => el.click());

show the list of files selected from different directories and ability to remove the files

I am trying to attach some files (zero/single/multiple) and send them as attachments to an email using ANGULARJS and spring.
One thing noticed is when selecting the files from multiple directories only the recently selected file is shown and previous selected file is not shown. How can I show all the files selected by the user from different directories too and give the ability to delete the file (all files or one file) before submitting the form.
Demo:http://plnkr.co/edit/M3f0TxHNozRxFEnrqyiF?p=preview
html:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
TO: <input type="text" name="to" id="to" ng-model="to" required ></input><br>
Subject : <input type="text" name="subject" id="subject" ng-model="subject"></input>
<br>Attachment: <input type="file" ng-file-model="files" multiple /> <br>
<p ng-repeat="file in files">
{{file.name}}
</p>
<textarea rows="20" maxlength=35000 name="message" ng-model="message" ></textarea>
<button type="button" ng-click="upload()">Send</button>
</body>
js:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.files = [];
$scope.upload=function(){
alert($scope.files.length+" files selected ... Write your Code to send the mail");
};
});
app.directive('ngFileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.ngFileModel);
var isMultiple = attrs.multiple;
var modelSetter = model.assign;
element.bind('change', function () {
var values = [];
angular.forEach(element[0].files, function (item) {
var value = {
// File Name
name: item.name,
//File Size
size: item.size,
//File URL to view
url: URL.createObjectURL(item),
// File Input Value
_file: item
};
values.push(value);
});
scope.$apply(function () {
if (isMultiple) {
modelSetter(scope, values);
} else {
modelSetter(scope, values[0]);
}
});
});
}
};
}]);
The default browser behavior is showing currently selected files, to cahnge that you've to customize that filed. And also, I saw your custom directive code, it doesn't allow to select multiple files from different directories.
So, what you can do is, create another scope variable & every time user selects file/files you push those files to this array. In this way you've have set of all selected files from same/different directories and then you can have delete functionality over each file which's ultimately going to be updated.
Updated html view part:
Attachment: <input type="file" ng-file-model="files" multiple /><br>
<p ng-repeat="file in filesToUpload track by $index">
{{file.name}} <span class="delete-file" ng-click="deleteFile($index)">X</span>
</p>
And for this new array update directive scope.$apply part as:
scope.$apply(function () {
if (isMultiple) {
modelSetter(scope, values);
} else {
modelSetter(scope, values[0]);
}
if(values){
scope.filesToUpload = scope.filesToUpload.concat(values);
}
});
In controller have deleteFile function as:
$scope.deleteFile = function(index){
$scope.filesToUpload.splice(index, 1);
};
Working Demo Example
Now user'll be able to delete files anytime. But the input field will still show the last selected file/files and after deleting particular file also it'll not change its status so for that you can just hide field by opacity: 0; css & then create customized Upload button & from that trigger click on actual hidden file input element.
Update: Check this update of same code with custom upload button:
Plunker Example

How to select an option from dropdown select

I can click the selector but my question is how to select one of the options from the dropdown list?
await page.click('#telCountryInput > option:nth-child(4)')
Click the option using CSS selector does not work.
For example, select a country code from a list like below:
Puppeteer v0.13.0 has page.select() method, which does exactly that. You just have to give it the value to select. So, assuming you have an <option value="my-value"> in your <select>:
await page.select('#telCountryInput', 'my-value')
For dropdown component, I think we should consider 2 situations:
native HTML select element
component written by JS, composed of a button and a list of options, take bootstrap dropdown as example
For the second situation, I think click can solve the problem.
For the first situation, I just found 2 ways to do this:
page.select
elementHandle.type (notice updated on 27/04/2018)
page.select is a new feature added in v0.12.0.
For example you have a select element:
<label>Choose One:
<select name="choose1">
<option value="val1">Value 1</option>
<option value="val2">Value 2</option>
<option value="val3">Value 3</option>
</select>
</label>
You have two ways to select second option 'Value 2'.
// use page.select
await page.select('select[name="choose1"]', 'val2');
// use elementHandle.type
const selectElem = await page.$('select[name="choose1"]');
await selectElem.type('Value 2');
Normally elementHandle.type is used to type in text in input textbox, but since it
Focuses the element, and then sends a keydown, keypress/input, and keyup event for each character in the text.
select HTML element has input event, so that this method works.
And I personally think elementHandle.type is better since it's not required to know the option value attribute, only the label/name what man can see.
27/04/2018 Updated
I previously used elementHandle.type only on Mac OSX. Recently, my colleague reported a bug related to this. He is using Linux/Win. Also, we are both using puppeteer v1.3.0.
After trial and error, we found that this elementHandle.type can assign the value to the <select> element, but this won't trigger the change event of the element.
So I no longer recommend using elementHandle.type on <select>.
Finally, we followed this comment to dispatch change event manually. It's like:
// use manually trigger change event
await page.evaluate((optionElem, selectElem) => {
optionElem.selected = true;
const event = new Event('change', {bubbles: true});
selectElem.dispatchEvent(event);
}, optionElem, selectElem);
For native selectboxes, my solution was to execute some JS on the page itself:
await page.evaluate(() => {
document.querySelector('select option:nth-child(2)').selected = true;
})
I landed here from a message where someone was asking how to select the first option from a dropdown. This is how I just worked out how to do it:
await page.click('.select-input');
await page.waitFor(300);
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Enter');
The above code first selects the relevant input. I then set a wait because mine wasn't loading quick enough. Then I used keyboard presses to navigate down to the first option.
Turn out this is easier than what I thought because the dropdown list is NOT a native HTML selction&option combination, therefore, I can actually use the code below to select the target I want.
await page.click('#telCountryInput')
await page.click('#select2-telCountryInput-results > li:nth-child(4)')
Page.select doesn't always work for me and page.type is unreliable as well. Today I came up with:
await page.evaluate((css, text) => {
let sel = document.querySelector(css)
for(let option of [...document.querySelectorAll(css + ' option')]){
if(text === option.text){
sel.value = option.value
}
}
}, '#telCountryInput', 'my-value')
#huagang
Your idea is amazing, I extended the value attribute
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>example</title>
</head>
<body>
<form id="add" method="post" action="/detail">
<label for="title"></label>
<input id="title" name="title">
<label for="tag">Tag</label>
<select id="tag">
<option value="1">java</option>
<option value="2">python</option>
<option value="3">kotlin</option>
</select>
</form>
<button id="submit" onclick="submitHandle()">Submit</button>
<script>
const submitHandle = () => {
document.getElementById('add').submit()
}
</script>
</body>
</html>
expect_value = '3'
select_tag = '#tag'
# extract all options value
option_texts = []
for option_ele in await page.querySelectorAll(f'{select_tag} > option'):
text = await page.evaluate('(element) => ({"value":element.value,"text":element.textContent})', option_ele)
option_texts.append(text)
value = ''
for v in option_texts:
if v.get('text') == expect_value:
value = v.get('value')
break
await page.select(select_tag, value)
In pyppeteer, when select by text, i can do this :
Example page with fastapi server
"""
filename: example.py
Note:
When run this example, recommend create a virtualenv by tools, like pipenv. And install dependencies.
Install dependencies:
```shell
pipenv install fastapi uvicorn python-multipart
```
Run server:
```shell
pipenv run python example.py
# pipenv run uvicorn --reload example:app
```
"""
import logging
import uvicorn
from fastapi import FastAPI, Form
from pydantic import BaseModel
from starlette.responses import HTMLResponse
HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>example</title>
</head>
<body>
<form id="add" method="post" action="/add">
<label for="title"></label>
<input id="title" name="title">
<label for="tag">Tag</label>
<select id="tag" name="tag">
<option>java</option>
<option>python</option>
<option>kotlin</option>
</select>
</form>
<button id="submit" onclick="submitHandle()">Submit</button>
<script>
const submitHandle = () => {
document.getElementById('add').submit()
}
</script>
</body>
</html>
"""
console_handler = logging.StreamHandler()
console_handler.setLevel(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
logger.addHandler(console_handler)
app = FastAPI()
class PostModel(BaseModel):
title: str
tag: str
#app.get('/posts')
def posts():
return HTMLResponse(content=HTML)
#app.post('/add')
def detail(title: str = Form(...), tag: str = Form(...)) -> PostModel:
post = PostModel(title=title, tag=tag)
logger.info(f'Add a blog. Detail: "{post.json()}"')
return post
if __name__ == '__main__':
uvicorn.run(app) # noqa
Example python spider code
import asyncio
import logging
from pyppeteer import launch
console_handler = logging.StreamHandler()
console_handler.setLevel(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
logger.addHandler(console_handler)
async def post_spider():
"""Open page and add value in form, then submit."""
browser = await launch(headless=False)
try:
page = await browser.newPage()
await page.goto('http://127.0.0.1:8000/posts')
expect_value = 'python'
title_element = await page.querySelector('#title')
await title_element.type('I love python, and python love me.')
# # If it does not work.
# await page.select('#tag', expect_value)
tag_element = await page.querySelector('#tag')
# #Extract all options value
# options_text = await page.querySelectorAllEval(
# '#tag > option',
# 'options => options.map(option => option.value)'
# )
options_text = await tag_element.querySelectorAllEval(
'option',
'options => options.map(option => option.value)'
)
# # Check expect value in options
if expect_value in options_text:
# # Use JavaScript set select element value that in options.
await page.querySelectorEval('#tag', f'element => element.value = "{expect_value}"')
tag_selected_value = await page.querySelectorEval('#tag', 'element => element.value')
logger.info(f'Selected tag element value is "{tag_selected_value}"')
submit_ele = await page.querySelector('#submit')
await submit_ele.click()
finally:
await browser.close()
if __name__ == '__main__':
asyncio.run(post_spider())
Note:
You can use evaluate a JavaScript to set one of options text to their select, if the text not in options, the select's value is not change.
This is python example, its use is similar to puppeteer and I would like to record it here to help more people.
My env:
Python: 3.10
pyppeteer: 0.2.6
I combined 2 answers and wrapped them in a function:
async function selectByText(page, selector, value) {
return await page.evaluate(
(css, text) => {
let sel = document.querySelector(css)
for (let option of [...document.querySelectorAll(css + ' option')]) {
if (text === option.text) {
sel.value = option.value
}
}
const event = new Event('change', { bubbles: true })
sel.dispatchEvent(event)
},
selector,
value,
)
}