108

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:

screenshot of the select element

XY L
  • 25,431
  • 14
  • 84
  • 143

9 Answers9

159

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

zaboco
  • 2,798
  • 1
  • 20
  • 16
  • is there a way to get all these values? – arslan Dec 13 '17 at 07:22
  • 1
    @arslan, from the documentation, it seems that there is no such option. But, assuming you store all the values, you can call `await page.select('#telCountryInput', ...allValues)` – zaboco Dec 13 '17 at 12:30
  • This API seems even worse han emulating keyboard input. How is this supposed to support i18n UIs? – user239558 Jan 17 '21 at 01:14
  • I don't really understand the question? Why shouldn't it support i18n UIs? And how would an API that better supports i18n look like? – zaboco Jan 18 '21 at 09:19
  • This method is invalid when option has no value property. eg: `` . – huagang Mar 17 '21 at 11:03
  • Why is `await page.select('#distId', "1");` on this page: https://covid19jagratha.kerala.nic.in/home/addHospitalDashBoard – Vipin Verma May 26 '21 at 06:16
50

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:

  1. page.select
  2. 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);
bambooom
  • 664
  • 6
  • 15
  • 3
    Your update for manually dispatching the event saved me - been wracking my brain with a weird bug for weeks! – Braydie Jul 05 '19 at 05:52
  • 1
    Awesome, this solved my problem. I had the select element and option element, but no unique class or id for the select, and I couldn't use `page.select` this was the perfect solution :D – user3014373 Jul 23 '19 at 00:22
  • How do you set the optionElem when you trigger the change? – Saurabh Mhase May 04 '20 at 15:01
  • Sorry for my previous comment - for some reason `await select.press('PageDown')` doesn't select the last option, in my case it selects the one before the last. – Mike Shiyan Sep 24 '21 at 19:33
18

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.devries
  • 8,747
  • 1
  • 27
  • 29
13

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.

Mark
  • 131
  • 1
  • 3
6

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)')
XY L
  • 25,431
  • 14
  • 84
  • 143
1

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')
pguardiario
  • 53,827
  • 19
  • 119
  • 159
1

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
huagang
  • 181
  • 1
  • 11
1

@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)

1

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,
    )
}
sietzekeuning
  • 43
  • 1
  • 5