【问题标题】:How to select an option from dropdown select如何从下拉选择中选择一个选项
【发布时间】:2022-01-19 16:21:22
【问题描述】:

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

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

单击使用 CSS 选择器的选项不起作用。

例如,从如下列表中选择一个国家/地区代码:

【问题讨论】:

    标签: puppeteer


    【解决方案1】:

    Puppeteer v0.13.0 具有 page.select() 方法,正是这样做的。你只需要给它选择的值。所以,假设你的<select> 中有一个<option value="my-value">

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

    【讨论】:

    • 有没有办法获取所有这些值?
    • @arslan,从文档来看,似乎没有这样的选项。但是,假设你存储了所有的值,你可以调用await page.select('#telCountryInput', ...allValues)
    • 这个 API 在模拟键盘输入方面似乎更糟糕。这应该如何支持 i18n UI?
    • 我不太明白这个问题?为什么它不应该支持 i18n UI?更好地支持 i18n 的 API 会是什么样子?
    • 该方法在option没有value属性时无效。例如:<option>foo</option> .
    【解决方案2】:

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

    • 原生 HTML select 元素
    • JS编写的组件,由一个按钮和一个选项列表组成,以bootstrap dropdown为例

    对于第二种情况,我觉得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>
    

    您有两种方法可以选择第二个选项“值 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 元素有输入事件,所以这个方法有效。

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

    27/04/2018 更新

    我之前只在 Mac OSX 上使用过elementHandle.type。最近,我的同事报告了一个与此相关的错误。他正在使用Linux/Win。另外,我们都在使用 puppeteer v1.3.0。

    经过反复试验,我们发现这个elementHandle.type可以给&lt;select&gt;元素赋值,但这不会触发元素的change事件。
    所以我不再推荐在&lt;select&gt; 上使用elementHandle.type

    最后,我们跟着this comment手动调度了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);
    

    【讨论】:

    • 您手动发送事件的更新拯救了我 - 几周来一直在用一个奇怪的错误破坏我的大脑!
    • 太棒了,这解决了我的问题。我有选择元素和选项元素,但选择没有唯一的类或 ID,我不能使用 page.select 这是完美的解决方案:D
    • 触发变更时如何设置optionElem?
    • 对不起我之前的评论 - 由于某种原因await select.press('PageDown') 没有选择最后一个选项,在我的情况下它选择了最后一个选项。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      事实证明这比我想象的要容易,因为下拉列表不是原生 HTML selction&option 组合,因此,我实际上可以使用下面的代码来选择我想要的目标。

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

      【讨论】:

      • 你的html源代码是什么?不知道源代码就很难阅读解决方案。
      【解决方案5】:

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

      await page.click('.select-input');
      await page.waitFor(300);
      await page.keyboard.press('ArrowDown');
      await page.keyboard.press('Enter');
      

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

          • 这能回答问题吗?
          【解决方案8】:

          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

          【讨论】:

            猜你喜欢
            • 2018-09-26
            • 1970-01-01
            • 2017-01-11
            • 1970-01-01
            • 2017-12-05
            • 1970-01-01
            • 2021-11-15
            • 1970-01-01
            • 2017-04-30
            相关资源
            最近更新 更多