如何从下拉选择框中选择一个选项

109

我可以点击选择器,但我的问题是如何从下拉列表中选择一个选项?

await page.click('#telCountryInput > option:nth-child(4)')

使用CSS选择器点击选项无效。

例如,从像下面这样的列表中选择一个国家代码:

screenshot of the select element

9个回答

161
Puppeteer v0.13.0增加了page.select()方法,可以精准地选择元素。只需提供所需的选项值即可。假设您的<select>中有一个<option value="my-value">

await page.select('#telCountryInput', 'my-value')


有没有一种方法可以获取所有这些值? - arslan
1
@arslan,从文档来看,似乎没有这样的选项。但是,假设您存储了所有值,您可以调用await page.select('#telCountryInput', ...allValues) - zaboco
这个 API 看起来甚至比模拟键盘输入还糟糕。它该如何支持国际化 UI 呢? - user239558
我不太明白这个问题?为什么它不应该支持i18n界面?那么一个更好地支持国际化的API会是什么样子呢? - zaboco
当选项没有值属性时,此方法无效。例如:<option>foo</option> - huagang
为什么在此页面上 await page.select('#distId', "1");:https://covid19jagratha.kerala.nic.in/home/addHospitalDashBoard - Vipin Verma

50

对于下拉组件,我认为我们应该考虑两种情况:

  • 原生的HTML select 元素
  • 由JS编写的组件,由一个按钮和一系列选项列表组成,以Bootstrap下拉菜单为例

对于第二种情况,我认为可以使用click来解决问题。

对于第一种情况,我找到了两种方法:

  1. page.select
  2. elementHandle.type(注意更新日期为2018年4月27日)

page.select是在v0.12.0中新增的功能。

例如,您有一个选择元素:

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

你有两种方式选择第二个选项“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');

通常情况下,elementHandle.type 用于在输入框中输入文本,但是由于它会使元素获得焦点并为文本中的每个字符发送 keydown、keypress/input 和 keyup 事件,所以这种方法也适用于 select HTML 元素具有 input 事件的情况。

我个人认为 elementHandle.type 更好,因为它不需要知道选项值属性,只需要标签/名称即可。

2018年4月27日 更新

我之前只在 Mac OSX 上使用 elementHandle.type。最近,我的同事报告了一个相关的 bug。他正在使用 Linux/Win,并且我们都在使用 puppeteer v1.3.0。

经过试验和错误,我们发现这个 elementHandle.type 可以将值分配给 <select> 元素,但这不会触发元素的 change 事件。
因此,我不再建议在 <select> 上使用 elementHandle.type

最后,我们按照此评论手动分派 change 事件,像这样:

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

3
你的更新关于手动分派事件对我非常有用——我已经为一个奇怪的错误苦恼了数周! - Braydie
1
太棒了,这解决了我的问题。我有选择元素和选项元素,但没有唯一的类或ID用于选择,我无法使用page.select,这是完美的解决方案:D - user3014373
当您触发更改时,如何设置optionElem选项元素? - Saurabh Mhase
抱歉我的上一条评论 - 不知何故 await select.press('PageDown') 没有选择最后一个选项,在我的情况下它选择了倒数第二个。 - Mike Shiyan

18

对于原生下拉选择框,我的解决方案是在页面本身上执行一些JS:

await page.evaluate(() => {
  document.querySelector('select option:nth-child(2)').selected = true;
})

13

我从一条信息中来到这里,有人问如何从下拉列表中选择第一个选项。 这是我刚刚想出的解决方法:

await page.click('.select-input');
await page.waitFor(300);
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Enter');
上述代码首先选择相关输入。然后我设置了一个等待时间,因为我的加载速度不够快。然后我使用键盘按键向下导航到第一个选项。

上述代码首先选择相关输入。然后我设置了一个等待时间,因为我的加载速度不够快。然后我使用键盘按键向下导航到第一个选项。


2
这在Windows上可以运行,但似乎在MacOS上无法运行。 - Roel

6

原来这比我想的要简单,因为下拉列表不是原生的HTML选择和选项组合,因此,我实际上可以使用下面的代码选择我想要的目标。

  await page.click('#telCountryInput')
  await page.click('#select2-telCountryInput-results > li:nth-child(4)')

6
你的HTML源代码是什么?不知道源代码,很难阅读解决方案。 - user938363

1

@huagang

你的想法很棒,我扩展了value属性。

<!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
这个回答解决了你的问题吗? - Vega

1

我将两个答案结合起来,并将它们封装在一个函数中:

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,
    )
}

1

Page.select 对我来说并不总是有效,而 page.type 也不太可靠。今天我想到了以下方法:

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

0
pyppeteer中,当按文本选择时,我可以这样做:

使用fastapi服务器的示例页面

"""
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

Python爬虫示例代码

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


注意:

您可以使用JavaScript来评估并将其中一个选项的文本设置为其选择,如果文本不在选项中,则选择的值不会更改。

这是Python示例,它的用法类似于puppeteer,我想在这里记录它以帮助更多的人。

我的环境:

  • Python: 3.10
  • pyppeteer: 0.2.6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接