Resolução de ambiguidade de capivara


97

Como faço para resolver a ambigüidade na Capivara? Por algum motivo, preciso de links com os mesmos valores em uma página, mas não consigo criar um teste, pois recebo o erro

Failure/Error: click_link("#tag1")
     Capybara::Ambiguous:
       Ambiguous match, found 2 elements matching link "#tag1"

A razão pela qual não posso evitar isso é por causa do design. Estou tentando recriar a página do Twitter com tweets / tags à direita e as tags à esquerda da página. Portanto, será inevitável que a página de links idênticos apareça na mesma página.


Você pode postar algum código também?
Heena Hussain

8
Você não deve atribuir o mesmo id a dois elementos na página. Se você tiver links idênticos, não atribua um id aos elementos, use uma classe.
Chris Salzberg

Respostas:


147

Minha solução é

first(:link, link).click

ao invés de

click_link(link)

6
Isso está detalhado no Guia de atualização do Capybara, que pode ser útil se você teve esse problema.
Ritchie

1
A partir do Capivara 2.0, não faça isso a menos que seja absolutamente necessário. Veja a resposta de @ Andrey abaixo e a explicação sobre Ambiguous Matches no guia de atualização no link acima.
Jim

4
Especificamente, o Capybara 2.0 tem uma lógica de espera inteligente para garantir que as especificações passem ou falhem de forma consistente em máquinas de velocidades de processamento diferentes enquanto espera apenas o tempo mínimo necessário. Usar firstcomo sugerido acima, a menos que você saiba absolutamente o que está fazendo, provavelmente resultará em especificações que são aprovadas para você, mas falham em um build de CI ou na máquina de um colega.
Jim

1
Para uma boa discussão, consulte: robots.thoughtbot.com/…
Jim

74

Tal comportamento da Capivara é intencional e acredito que não deva ser corrigido como sugerido na maioria das outras respostas.

Versões do Capybara anteriores à 2.0 retornavam o primeiro elemento em vez de gerar exceção, mas os mantenedores posteriores do Capybara decidiram que é uma má ideia e é melhor aumentá-la. Foi decidido que em muitas situações o retorno do primeiro elemento leva a não retornar o elemento que o desenvolvedor queria que fosse retornado.

A resposta mais votada aqui recomendo usar firstou em allvez de findmas:

  1. alle firstnão espere até que o elemento com tal localizador apareça na página, embora findespere
  2. all(...).firste firstnão o protegerá de situações em que, no futuro, outro elemento com esse localizador possa aparecer na página e, como resultado, você poderá encontrar o elemento incorreto

Portanto , é aconselhável escolher outro localizador menos ambíguo : por exemplo, selecione o elemento por id, classe ou outro localizador css / xpath para que apenas um elemento corresponda a ele.


Como observação, aqui estão alguns localizadores que geralmente considero úteis para resolver ambiguidades:

  • find('ul > li:first-child')

    É mais útil do first('ul > li')que esperar até que o primeiro liapareça na página.

  • click_link('Create Account', match: :first)

    É melhor do first(:link, 'Create Account').clickque esperar até que pelo menos um link Criar conta apareça na página. No entanto, acredito que seja melhor escolher um localizador exclusivo que não apareça na página duas vezes.

  • fill_in('Password', with: 'secret', exact: true)

    exact: true diz a Capivara para encontrar apenas correspondências exatas, ou seja, não encontrar "Confirmação de senha"


7
Esta deve ser a melhor resposta. Sempre tente usar um seletor que fará uso dos recursos de espera integrados no Capivara.
tgf

Obrigado. Tentei usar: primeiro mas percebi que só funciona em jQuery. O que eu procurava é: primeiro filho
Sobrecarga119 de


24

NOVA RESPOSTA:

Você pode tentar algo como

all('a').select {|elt| elt.text == "#tag1" }.first.click

Pode haver uma maneira de fazer isso que faz melhor uso da sintaxe Capybara disponível - algo parecido com, all("a[text='#tag1']").first.clickmas não consigo pensar na sintaxe correta de improviso e não consigo encontrar a documentação apropriada. Dito isto, é um pouco de uma situação estranha para começar, ter duas <a>marcas com o mesmo id, classe texto. Existe alguma chance de eles serem filhos de divs diferentes, já que você poderia então fazer seu find withinsegmento apropriado do DOM. (Seria bom ver um pouco do código-fonte HTML).


RESPOSTA ANTIGA: (onde eu pensei que '# tag1' significava que o elemento tinha id"tag1")

Em qual dos links você deseja clicar? Se for o primeiro (ou não importa), você pode fazer

find('#tag1').click

Caso contrário, você pode fazer

all('#tag1')[1].click

para clicar no segundo.


Essa solução no primeiro pode funcionar, mas o problema agora é que pode ser confundido com um id de css --------- Falha / Erro: find ('# tag1'). Click # ou all ('# tag1 ') [0] .click Capybara :: ElementNotFound: Incapaz de encontrar css "# tag1"
neilmarion 01 de

find('#tag1')significa que você deseja encontrar apenas um elemento com id tag1. A exceção é levantada porque há vários elementos com id tag1na página
Andrei Botalov

Você pode fazer all(:xpath, '//a[text()="#tag1"]').first.click.
Shuhei Kagawa

9

Você pode garantir que encontrará o primeiro usando match:

find('.selector', match: :first).click

Mas o mais importante, você provavelmente não quer fazer isso , pois isso levará a testes frágeis que estão ignorando o cheiro de código de saída duplicada, o que por sua vez leva a falsos positivos que continuam funcionando quando deveriam ter falhado, porque você removeu um correspondente elemento, mas o teste felizmente encontrou o outro.

A melhor aposta é usar within:

within('#sidebar') do
  find('.selector).click
end

Isso garante que você está encontrando o elemento que espera encontrar, enquanto ainda aproveita os recursos de espera automática e repetição automática do Capivara (que você perde se usar find('.selector').click), e torna muito mais claro qual é a intenção.


7

Para adicionar ao corpo de conhecimento existente aqui:

Para os testes de JS, Capybara deve manter dois threads (um para RSpec, um para Rails) e um segundo processo (o navegador) em sincronia. Ele faz isso aguardando (até o tempo de espera máximo configurado) na maioria dos comparadores e métodos de localização de nós.

Capivara também tem métodos que não esperam, principalmente Node#all. Usá-los é como dizer às suas especificações que você gostaria que falhassem intermitentemente.

A resposta aceita sugere page.first('selector'). Isso é indesejável, pelo menos para especificações JS, porque Node#firstusaNode#all .

Dito isso, Node#first vou esperar se você configurar Capivara assim:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

Esta opção foi adicionada no Capybara 2.5.0 e é falsa por padrão.

Como Andrei mencionou, você deve usar

find('selector', match: :first)

ou mude seu seletor. Ambos funcionarão bem, independentemente da configuração ou driver.

Para complicar ainda mais as coisas, em versões antigas do Capybara (ou com uma opção de configuração habilitada), #findfelizmente ignorará a ambigüidade e apenas retornará o primeiro seletor correspondente. Isso também não é ótimo, pois torna suas especificações menos explícitas, o que imagino ser o motivo pelo qual não é mais o comportamento padrão. Vou omitir os detalhes porque já foram discutidos acima.

Mais recursos:


5

Devido a esta postagem , você pode corrigi-lo através da opção "combinar":

Capybara.configure do |config|
  config.match = :prefer_exact
end

2

Considerando todas as opções acima, você também pode tentar isso

find("a", text: text, match: :prefer_exact).click

Se você estiver usando pepino, também pode seguir

Você pode passar o texto como um parâmetro das etapas do cenário que podem ser uma etapa genérica para reutilizar novamente

Algo como When a user clicks on "text" link

E na definição da etapa When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

Dessa forma, você pode reutilizar a mesma etapa, minimizando as linhas de código e seria fácil escrever novos cenários de pepino


0

Para evitar erros ambíguos no pepino.

Solução 1

first("#tag1").click

Solução 2

Cucumber features/filename.feature --guess
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.