Das interações da página | Puppeteer :
Locators é a maneira recomendada de selecionar um elemento e interagir com ele. Locators encapsulam as informações sobre como selecionar um elemento e permitem que o Puppeteer espere automaticamente que o elemento esteja presente no DOM e esteja no estado certo para a ação. Você sempre instancia um locator usando a função page.locator() ou frame.locator(). Se a API do locator não oferecer uma funcionalidade que você precisa, você ainda pode usar APIs de nível inferior, como page.waitForSelector() ou ElementHandle.
Esta tabela resume meu entendimento sobre a diferença entre locator()
e $()
:
Page método |
Classe retornada | Esperar que o elemento seja apresentado no DOM? | Precisar await ? |
Métodos de entrada |
---|---|---|---|---|
locator() |
Locator |
Sim (tente novamente até obter sucesso ou tempo limite) | Não | Só tenho click , hover , scroll , |
$() |
ElementHandle |
Não | Sim | Vários |
Tenho algumas perguntas:
- Se
locator()
espera que o elemento esteja presente no DOM, então não é que ele tem que terawait
? Similarmente, se$()
não espera, então não é que ele não precisa deawait
? - Por que
$()
um método que não espera que elementos sejam apresentados no DOM tem mais métodos de entrada de dispositivo do que aquele que espera por eles? - Será que por baixo do capô
locator()
está a combinação dewaitForSelector()
e$()
?
Pergunta relacionada: O que torna um elemento não clicável ao usar locator()
, mas clicável ao usar $()
?
Antes de mergulhar em suas perguntas específicas, sugiro verificar este tópico semelhante sobre fundamentos do localizador: Compreendendo as promessas do localizador do Playwright . O Playwright herdou originalmente métodos do estilo Puppeteer como
page.$()
, então adotou localizadores e descontinuou ou desencorajou ospage.$()
métodos do estilo legado -. Mais tarde, o Puppeteer pegou emprestado localizadores do Playwright. As bibliotecas têm uma abordagem bem diferente para localizadores, mas a ideia fundamental é a mesma.Terminologia: como
page.$()
,page.waitForSelector()
e outras funções retornam ElementHandles, chamarei isso de "API de identificador de elemento".A principal diferença entre as APIs locator e element handle é que os locators são preguiçosos e não fazem nada até que uma ação, como
.click()
or.fill()
, seja chamada neles. A chamadapage.locator()
declara uma estratégia de seleção que pode potencialmente ter ações chamadas nela mais tarde. Não háawait
porque ainda não há nenhuma ação e nenhuma chamada de rede de automação de navegador CDP (Chrome Devtools Protocol) assíncrona.Por outro lado, chamadas baseadas em identificador de elemento como
$()
executar uma consulta imediata e retornar um identificador de elemento que representa um link para um elemento DOM real que pode ser manipulado posteriormente com métodos comoelementHandle.click()
. Dentro da API de identificador de elemento,waitForSelector()
é comparável a parte do que um localizador faz. A espera automática por padrão é a primeira vantagem dos localizadores sobre a API de identificador de elemento.Quando você chama
await page.locator("p").click()
, a.click()
parte é uma ação encadeada. Considere:contra
A diferença pode parecer trivial no exemplo acima, mas o localizador faz apenas uma chamada CDP para o navegador, em vez de duas, e consulta o elemento no momento exato da ação, evitando a chance de a página mudar entre as chamadas CDP.
Por outro lado, a
waitForSelector()
versão faz duas chamadas, abrindo uma condição de corrida devido às ações separadas de consulta e clique. Se o elemento for invalidado ou mudar inesperadamente, pode ocorrer uma falha difícil de depurar.A versão locator pode ser declarada uma vez e reutilizada facilmente, com muito "espaço" (em termos de código e tempo) entre declaração e uso. Isso facilita abordagens de teste de melhores práticas como o POM e torna testes grandes mais fáceis de manter.
Os localizadores são facilmente componíveis, então você pode encadear mais filtros à vontade e ainda executar uma consulta sem condições de corrida por ação.
Note que, como o Playwright é mais voltado para testes do que o Puppeteer, as vantagens dos localizadores são muito mais aparentes no Playwright. No momento em que este artigo foi escrito, a implementação do localizador do Playwright é significativamente mais avançada do que a do Puppeteer, e o Playwright é muito mais opinativo sobre encorajar os usuários a usarem localizadores exclusivamente.
Quanto às suas perguntas específicas:
Espera automática ou não é irrelevante para saber se você precisa usar
await
.await
é usado sempre que há uma chamada de rede, uma chamada de sistema ou comunicação entre processos que precisa enviar um comando para outro processo ou sistema operacional e esperar por uma resposta. O Node é single-threaded, então para operar eficientemente, ele usa construções de código assíncronas como promessas para permitir que ele execute outras tarefas enquanto a chamada de rede está em andamento.Como o navegador que você está automatizando com o Puppeteer é um processo separado do Node e expõe um soquete para comunicação de rede, qualquer chamada CDP é assíncrona, independentemente de a chamada envolver ou não a espera por uma condição. Então, as únicas chamadas que não precisam
await
são aquelas que não envolvem chamadas CDP, comopage.locator()
. Não misture as palavras "wait" e "await"--totalmente diferente aqui! Veja Por que quase todas as chamadas do Puppeteer são assíncronas? para detalhes.Se você quer dizer "mais opções/parâmetros de entrada". Isso ocorre porque
page.$()
é um método "selecionar e agir em um" que inclui um seletor (o parâmetro string) e imediatamente realiza uma ação CDP, indo para o navegador e consultando o elemento, então retornando um identificador de elemento de volta para o Node.As ações normalmente têm timeouts e outros parâmetros de configuração que são desacoplados da
locator()
chamada, que é estritamente uma configuração de seleção. Na API do localizador, timeouts e outras opções de configuração são especificadas por meio de encadeamento e em chamadas de ação, aprimorando a reutilização.Se você quer dizer o que diz (e parece que sim, com base nos comentários), a API do localizador do Puppeteer é simplesmente menos madura do que a API do manipulador de elementos, então há menos métodos disponíveis no momento. Mas se o Puppeteer seguir o exemplo com o Playwright, sua API do localizador pode eventualmente se tornar completa o suficiente para permitir que os mantenedores descontinuassem a API do manipulador de elementos, como o Playwright fez.
Espero que tenha sido respondido acima.
locator()
apenas declara uma estratégia de seleção, mas não faz nada além disso. Então, não,locator()
não é semelhante a nenhum dos métodos, embora uma vez que um localizador tenha uma ação chamada nele, ele faça auto-wait comowaitForSelector()
. Você pode pensar em localizadores como semelhantes ao"p"
parâmetro de string que você passa parapage.$()
, exceto aumentado com métodos encadeáveis/componíveis.Embora eu concorde que a API de manipulação de elementos é de nível mais baixo do que a API do localizador, não é tecnicamente possível implementar a API do localizador a partir de chamadas de manipulação de elementos como
waitForSelector()
você. O motivo é o intervalo de tempo entre consultar um elemento e tomar uma ação sobre ele — os localizadores selecionam e agem de forma síncrona (no que diz respeito ao que acontece no contexto do navegador) em uma única chamada CDP, evitando identificadores obsoletos. Mas essa é uma janela bastante estreita para falha, então, realisticamente, você poderia aproximar os localizadores com identificadores de elementos (tentei fazer exatamente isso para uma biblioteca de scraping que criei).A propósito, há um terceiro estilo de API oferecido pela Puppeteer e Playwright que vale a pena mencionar. Vou chamá-lo de "API de avaliação" e consiste em
page.evaluate()
,page.$eval()
,page.$$eval()
,page.waitForFunction()
, e assim por diante. Como a API de manipulação de elementos, não há espera automática, e a consulta e a ação são reunidas em uma operação.A API de avaliação é de nível ainda mais baixo que os identificadores, embora não seja um subconjunto e não possa fazer muitas das coisas que os identificadores fazem, como emitir eventos confiáveis.
Mas, diferentemente da API de manipulação de elementos, cada consulta e ação na API de avaliação é síncrona e atômica, e a sintaxe para trabalhar dentro do navegador de forma síncrona é geralmente menos trabalhosa do que lidar com manipuladores de elementos. Chamadas de avaliação dão a você um grande controle preciso sobre a página.
No Puppeteer, meus scripts de web scraping e geração de PDF usam quase exclusivamente a API de avaliação, exceto para preencher campos de entrada, onde identificadores de elementos ou localizadores se tornam necessários. Isso pode mudar conforme a API de localizador do Puppeteer amadurece, mas mesmo que tenha alcançado paridade com o Playwright, os localizadores tendem a ser mais voltados para testes. Reutilização, componibilidade, eventos confiáveis e DRYness importam menos em scripts de scraping do que em testes.
Um pensamento final sobre localizadores é que o encadeamento de métodos é sintaticamente ergonômico em ambientes
async
/await
, evitando muitas dasconst foo = await ...
atribuições de variáveis intermediárias eawait (await page.$()).click()
sintaxe desagradável do tipo - em ambientes assíncronos não encadeados. O Puppeteer provavelmente leva os localizadores encadeados longe demais, movendo configurações como timeouts para chamadas encadeadas em vez de objetos de configuração, o que é trabalhoso em outros aspectos. Mas estou saindo um pouco do assunto aqui, então vou deixar essa discussão de lado por enquanto.