Skip to content

Mouse

O Pydoll controla o mouse de duas formas: através de um elemento que você encontrou, que é o que você quer na maior parte do tempo, ou em coordenadas brutas da página quando você precisa de posições precisas. Ambas suportam humanize=True, que move o cursor por um caminho curvo, com tempo humano, em vez de teleportar para o alvo.

Clicar em um elemento

O caso comum é clicar em um elemento que você já localizou com find() ou query(). Chame click() nele; você não calcula coordenadas, e o elemento é rolado para a área visível primeiro.

from pydoll.sync import Chrome

def main():
    with Chrome() as browser:
        tab = browser.start()
        tab.go_to('https://the-internet.herokuapp.com/add_remove_elements/')

        add_button = tab.find(text='Add Element')
        add_button.click()

        # o clique adicionou um botão Delete
        delete = tab.find(class_name='added-manually')
        print('Added:', delete.text())

main()
import asyncio

from pydoll import Chrome


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://the-internet.herokuapp.com/add_remove_elements/')

        add_button = await tab.find(text='Add Element')
        await add_button.click()

        # o clique adicionou um botão Delete
        delete = await tab.find(class_name='added-manually')
        print('Added:', await delete.text())

asyncio.run(main())

O click() recebe algumas opções:

# clica em um ponto deslocado do centro do elemento (pixels)
element.click(x_offset=10, y_offset=5)

# mantém o botão pressionado por mais tempo antes de soltar (segundos)
element.click(hold_time=0.3)

# humanizado: caminho curvo do cursor até o elemento, depois clique
element.click(humanize=True)
# clica em um ponto deslocado do centro do elemento (pixels)
await element.click(x_offset=10, y_offset=5)

# mantém o botão pressionado por mais tempo antes de soltar (segundos)
await element.click(hold_time=0.3)

# humanizado: caminho curvo do cursor até o elemento, depois clique
await element.click(humanize=True)

humanize=True funciona onde quer que o elemento esteja: no documento principal, dentro de um iframe (inclusive cross-origin) e dentro de um shadow root. Um iframe em outro processo tem coordenadas de viewport próprias, então seus elementos movem um cursor só deles, rastreado separado do da aba.

Clique em elemento vs coordenadas brutas

Prefira element.click(). Ele encontra a posição do elemento para você e sobrevive a mudanças de layout. Recorra à API de coordenadas abaixo apenas quando não há elemento a mirar, como clicar dentro de um <canvas> ou arrastar um controle por pixel.

Passar o mouse sobre um elemento

hover() move o cursor até o elemento e o deixa ali, que é como você abre menus que expandem no mouseover e tooltips que aparecem ao passar o mouse. O elemento é rolado para a área visível primeiro, e humanize=True move ao longo de um caminho curvo em vez de saltar.

menu = tab.find(class_name='nav-item', text='Produtos')
menu.hover()
tab.find(text='Notebooks', timeout=2).click()

# humanizado: caminho curvo do cursor até o elemento
menu.hover(humanize=True)
menu = await tab.find(class_name='nav-item', text='Produtos')
await menu.hover()
await (await tab.find(text='Notebooks', timeout=2)).click()

# humanizado: caminho curvo do cursor até o elemento
await menu.hover(humanize=True)

Dar um duplo clique em um elemento

double_click() envia dois cliques com o intervalo e as contagens de clique que a página precisa para disparar um evento dblclick, então seleção de texto, editores inline e handlers de "abrir item" respondem como a um duplo clique real. Aceita as mesmas opções x_offset, y_offset e humanize de click().

cell = tab.find(class_name='cell', text='Sem título')
cell.double_click()
tab.find(tag_name='input', timeout=2).type_text('Relatório Q3')
cell = await tab.find(class_name='cell', text='Sem título')
await cell.double_click()
await (await tab.find(tag_name='input', timeout=2)).type_text('Relatório Q3')

A API de mouse por coordenadas

tab.mouse clica, move e arrasta em coordenadas explícitas em pixels CSS, medidas a partir do canto superior esquerdo da página. Você geralmente obtém essas coordenadas a partir dos limites de um elemento (veja Arrastar um slider).

from pydoll.sync import Chrome
from pydoll.protocol.input.types import MouseButton

def main():
    with Chrome() as browser:
        tab = browser.start()
        tab.go_to('https://the-internet.herokuapp.com/')

        tab.mouse.move(500, 300)                        # move o cursor
        tab.mouse.click(500, 300)                       # clique esquerdo
        tab.mouse.click(500, 300, button=MouseButton.RIGHT)  # clique direito
        tab.mouse.double_click(500, 300)               # clique duplo
        tab.mouse.drag(100, 200, 500, 400)             # pressiona, move, solta

main()
import asyncio

from pydoll import Chrome
from pydoll.protocol.input.types import MouseButton


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://the-internet.herokuapp.com/')

        await tab.mouse.move(500, 300)                        # move o cursor
        await tab.mouse.click(500, 300)                       # clique esquerdo
        await tab.mouse.click(500, 300, button=MouseButton.RIGHT)  # clique direito
        await tab.mouse.double_click(500, 300)               # clique duplo
        await tab.mouse.drag(100, 200, 500, 400)             # pressiona, move, solta

asyncio.run(main())

O MouseButton (de pydoll.protocol.input.types) tem LEFT, MIDDLE e RIGHT. O click() também recebe click_count (passe 2 para um clique duplo) e todo método recebe o humanize (apenas por palavra-chave).

Para pressionar e soltar separadamente, down() e up() operam na posição atual do cursor:

tab.mouse.move(300, 400)
tab.mouse.down(button=MouseButton.LEFT)
tab.mouse.move(600, 400)     # arraste manualmente
tab.mouse.up(button=MouseButton.LEFT)
await tab.mouse.move(300, 400)
await tab.mouse.down(button=MouseButton.LEFT)
await tab.mouse.move(600, 400)     # arraste manualmente
await tab.mouse.up(button=MouseButton.LEFT)

O tab.mouse rastreia a posição do cursor entre chamadas, então down()/up() agem onde quer que o último move() ou click() tenha deixado.

Mover como um humano

Por padrão, um movimento ou clique salta direto para o alvo, o que é um indício comportamental. Passe humanize=True e o Pydoll move o cursor por um caminho curvo com tempo humano (uma duração baseada na Lei de Fitts, um perfil de velocidade em forma de sino, um pequeno tremor, e ocasional ultrapassagem com correção):

tab.mouse.move(500, 300, humanize=True)
tab.mouse.click(500, 300, humanize=True)
tab.mouse.drag(100, 200, 500, 400, humanize=True)
await tab.mouse.move(500, 300, humanize=True)
await tab.mouse.click(500, 300, humanize=True)
await tab.mouse.drag(100, 200, 500, 400, humanize=True)

Cursor humanizado percorrendo dois caminhos curvos

Dois movimentos humanizados: caminhos curvos com easing e um leve overshoot, não saltos retos.

Cliques humanizados em elementos funcionam da mesma forma. Como a posição é rastreada, clicar no elemento A e depois no elemento B traça uma curva natural de um para o outro:

# instantâneo: o cursor salta direto para cada alvo
tab.find(id='first').click()
tab.find(id='second').click()

# humanizado: o cursor curva naturalmente de um alvo para o próximo
tab.find(id='first').click(humanize=True)
tab.find(id='second').click(humanize=True)
# instantâneo: o cursor salta direto para cada alvo
await (await tab.find(id='first')).click()
await (await tab.find(id='second')).click()

# humanizado: o cursor curva naturalmente de um alvo para o próximo
await (await tab.find(id='first')).click(humanize=True)
await (await tab.find(id='second')).click(humanize=True)

Veja Interações humanizadas para o modelo completo de tempo e quando a humanização importa.

Ajustar o tempo

A física humanizada é configurável através de MouseTimingConfig. Atribua uma nova config a tab.mouse.timing:

from pydoll.interactions.mouse import MouseTimingConfig

tab.mouse.timing = MouseTimingConfig(
    fitts_a=0.070,               # tempo base de movimento (segundos)
    fitts_b=0.150,               # tempo adicionado por bit de dificuldade
    curvature_min=0.10,          # menor curvatura do caminho (fração da distância)
    curvature_max=0.30,          # maior curvatura do caminho
    tremor_amplitude=1.0,        # sigma do tremor da mão em pixels
    overshoot_probability=0.70,  # chance de ultrapassagem em movimentos rápidos e longos
    max_duration=2.5,            # limite para um único movimento (segundos)
)

Todo campo tem um valor padrão, então sobrescreva apenas o que precisar. Veja a dataclass MouseTimingConfig em pydoll/interactions/mouse.py para a lista completa.

Observar o cursor durante o ajuste

Defina tab.mouse.debug = True e o Pydoll desenha o caminho do cursor sobre uma sobreposição transparente: pontos azuis traçam o movimento, pontos vermelhos marcam os cliques. Use para verificar se os caminhos humanizados parecem naturais, depois desligue.

tab.mouse.debug = True
tab.mouse.click(500, 300, humanize=True)
tab.mouse.debug = False
tab.mouse.debug = True
await tab.mouse.click(500, 300, humanize=True)
tab.mouse.debug = False

Exemplos práticos

Arrastar um slider

Leia a posição do controle a partir dos seus limites, depois arraste de lá:

slider = tab.query('.slider-handle')
bounds = slider.get_bounds_using_js()   # {'x', 'y', 'width', 'height'}, pixels do viewport

start_x = bounds['x'] + bounds['width'] / 2
start_y = bounds['y'] + bounds['height'] / 2

tab.mouse.drag(start_x, start_y, start_x + 200, start_y, humanize=True)
slider = await tab.query('.slider-handle')
bounds = await slider.get_bounds_using_js()   # {'x', 'y', 'width', 'height'}, pixels do viewport

start_x = bounds['x'] + bounds['width'] / 2
start_y = bounds['y'] + bounds['height'] / 2

await tab.mouse.drag(start_x, start_y, start_x + 200, start_y, humanize=True)

Handle do slider arrastado por um caminho humanizado

Arrastando o handle por um caminho humanizado.

Passar o cursor sobre um menu

Mova o cursor até um elemento para acionar seu estado CSS :hover, sem clicar:

trigger = tab.query('.dropdown-trigger')
bounds = trigger.get_bounds_using_js()

tab.mouse.move(
    bounds['x'] + bounds['width'] / 2,
    bounds['y'] + bounds['height'] / 2,
    humanize=True,
)
trigger = await tab.query('.dropdown-trigger')
bounds = await trigger.get_bounds_using_js()

await tab.mouse.move(
    bounds['x'] + bounds['width'] / 2,
    bounds['y'] + bounds['height'] / 2,
    humanize=True,
)

Cursor entrando no gatilho do menu e abrindo o dropdown

O cursor entra no gatilho, o dropdown expande e para em um item.

Próximos passos