Capybara Ambiguity Resolution


97

ฉันจะแก้ไขความคลุมเครือใน Capybara ได้อย่างไร ด้วยเหตุผลบางประการฉันต้องการลิงก์ที่มีค่าเดียวกันในเพจ แต่ไม่สามารถสร้างการทดสอบได้เนื่องจากได้รับข้อผิดพลาด

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

สาเหตุที่เลี่ยงเรื่องนี้ไม่ได้เพราะการออกแบบ ฉันพยายามสร้างหน้าทวิตเตอร์ขึ้นมาใหม่โดยมีทวีต / แท็กทางด้านขวาและแท็กทางด้านซ้ายของหน้า ดังนั้นจึงหลีกเลี่ยงไม่ได้ที่หน้าลิงก์ที่เหมือนกันจะปรากฏในหน้าเดียวกัน


กรุณาโพสต์รหัสด้วยได้ไหม
Heena Hussain

8
คุณไม่ควรกำหนด id เดียวกันให้กับสององค์ประกอบในหน้า หากคุณจะมีลิงก์ที่เหมือนกันอย่ากำหนด id ให้กับองค์ประกอบให้ใช้คลาสแทน
Chris Salzberg

คำตอบ:


147

ทางออกของฉันคือ

first(:link, link).click

แทน

click_link(link)

6
นี่คือรายละเอียดในคู่มือการอัปเกรด Capybara ที่คุณอาจพบว่ามีประโยชน์หากคุณมีปัญหานี้
Ritchie

1
สำหรับ Capybara 2.0 อย่าทำเช่นนี้เว้นแต่คุณจะต้องทำอย่างแน่นอน ดูคำตอบของ @ Andrey ด้านล่างและคำอธิบายของ Ambiguous Matches ในคู่มือการอัปเกรดที่ลิงก์ด้านบน
jim

4
โดยเฉพาะ Capybara 2.0 มีตรรกะการรอที่ชาญฉลาดเพื่อให้แน่ใจว่าข้อมูลจำเพาะผ่านหรือล้มเหลวอย่างสม่ำเสมอในเครื่องจักรที่มีความเร็วในการประมวลผลต่างกันในขณะที่รอเวลาที่จำเป็นขั้นต่ำเท่านั้น การใช้firstตามที่แนะนำข้างต้นเว้นแต่คุณจะรู้แน่ชัดว่าคุณกำลังทำอะไรอยู่มีแนวโน้มที่จะส่งผลให้ข้อกำหนดที่ส่งผ่านสำหรับคุณ แต่ล้มเหลวในการสร้าง CI หรือบนเครื่องของเพื่อนร่วมงาน
jim

1
สำหรับการสนทนาที่ดีโปรดดู: robots.thoughtbot.com/…
jim

74

พฤติกรรมดังกล่าวของ Capybara เป็นความตั้งใจและฉันเชื่อว่ามันไม่ควรได้รับการแก้ไขตามที่แนะนำไว้ในคำตอบอื่น ๆ ส่วนใหญ่

Capybara เวอร์ชันก่อน 2.0 ส่งคืนองค์ประกอบแรกแทนที่จะเพิ่มข้อยกเว้น แต่ผู้ดูแล Capybara ในภายหลังตัดสินใจว่าเป็นความคิดที่ไม่ดีและควรยกระดับขึ้น มีการตัดสินใจว่าในหลาย ๆ สถานการณ์การส่งคืนองค์ประกอบแรกจะนำไปสู่การส่งคืนไม่ใช่องค์ประกอบที่นักพัฒนาต้องการส่งคืน

คำตอบที่ได้รับการโหวตมากที่สุดแนะนำให้ใช้firstหรือallแทนที่จะใช้findแต่:

  1. allและfirstอย่ารอจนกว่าองค์ประกอบที่มีตัวระบุตำแหน่งดังกล่าวจะปรากฏบนหน้าแม้ว่าfindจะรอก็ตาม
  2. all(...).firstและfirstจะไม่ปกป้องคุณจากสถานการณ์ที่ในอนาคตองค์ประกอบอื่นที่มีตัวระบุตำแหน่งดังกล่าวอาจปรากฏบนหน้าและคุณอาจพบองค์ประกอบที่ไม่ถูกต้อง

ดังนั้นจึงแนะนำให้เลือกตัวระบุตำแหน่งอื่นที่ไม่ชัดเจน : ตัวอย่างเช่นเลือกองค์ประกอบตาม id คลาสหรือตัวระบุตำแหน่ง css / xpath อื่น ๆ เพื่อให้มีเพียงองค์ประกอบเดียวเท่านั้นที่จะจับคู่


ดังที่ทราบต่อไปนี้คือตัวระบุตำแหน่งบางตัวที่ฉันมักคิดว่ามีประโยชน์เมื่อแก้ไขความไม่ชัดเจน:

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

    มีประโยชน์มากกว่าfirst('ul > li')ที่จะรอจนกว่าliจะปรากฏบนหน้าก่อน

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

    ดีกว่าfirst(:link, 'Create Account').clickที่จะรอจนกว่าลิงก์สร้างบัญชีอย่างน้อยหนึ่งลิงก์จะปรากฏบนหน้า อย่างไรก็ตามฉันเชื่อว่าควรเลือกตัวระบุตำแหน่งที่ไม่ซ้ำกันซึ่งไม่ปรากฏบนหน้าสองครั้ง

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

    exact: true บอกให้ Capybara ค้นหาเฉพาะรายการที่ตรงกันเท่านั้นเช่นไม่พบ "Password Confirmation"


7
นี่น่าจะเป็นคำตอบอันดับต้น ๆ พยายามใช้ตัวเลือกที่จะใช้ประโยชน์จากความสามารถในการรอคอยใน Capybara เสมอ
tgf

ขอบคุณ. ฉันพยายามใช้: ก่อนอื่น แต่ตระหนักว่าใช้ได้เฉพาะใน jQuery สิ่งที่ฉันกำลังมองหาคือลูกคนแรก
Overload119

26

วิธีแก้ปัญหาข้างต้นใช้งานได้ดี แต่สำหรับผู้ที่อยากรู้อยากเห็นคุณสามารถใช้ไวยากรณ์ต่อไปนี้ได้

click_link(link_name, match: :first)

คุณสามารถค้นหาข้อมูลเพิ่มเติมได้ที่นี่:

http://taimoorchangaizpucitian.wordpress.com/2013/09/06/capybara-click-link-different-cases-and-solutions/


24

คำตอบใหม่:

คุณสามารถลองสิ่งที่ชอบ

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

อาจมีวิธีในการดำเนินการนี้ซึ่งใช้ประโยชน์จากไวยากรณ์ Capybara ที่มีอยู่ได้ดีขึ้น - บางอย่างตามบรรทัดall("a[text='#tag1']").first.clickแต่ฉันไม่สามารถนึกถึงไวยากรณ์ที่ถูกต้องได้และฉันไม่พบเอกสารที่เหมาะสม ที่กล่าวว่ามันเป็นบิตของสถานการณ์ที่แปลกจะเริ่มต้นด้วยการมีสอง<a>แท็กด้วยกันid, classและข้อความ มีโอกาสใดบ้างที่พวกเขาเป็นลูกของ div ที่แตกต่างกันเนื่องจากคุณสามารถทำfind withinส่วนที่เหมาะสมของ DOM ได้ (จะช่วยให้เห็นแหล่งที่มา HTML ของคุณ)


คำตอบเก่า: (ที่ฉันคิดว่า '# tag1' หมายถึงองค์ประกอบมีid"tag1")

คุณต้องการคลิกลิงก์ใด หากเป็นครั้งแรก (หรือไม่สำคัญ) คุณสามารถทำได้

find('#tag1').click

มิฉะนั้นคุณสามารถทำได้

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

เพื่อคลิกอันที่สอง


วิธีแก้ปัญหาในวิธีแรกอาจใช้งานได้ แต่ปัญหาตอนนี้คืออาจเข้าใจผิดว่าเป็นรหัส css --------- ล้มเหลว / ข้อผิดพลาด: ค้นหา ('# tag1') คลิก # หรือทั้งหมด ('# tag1 ') [0] .click Capybara :: ElementNotFound: ไม่พบ css "# tag1"
neilmarion

find('#tag1')หมายความว่าคุณต้องการที่จะหาเพียงองค์ประกอบหนึ่งที่มี tag1ID มีการเพิ่มข้อยกเว้นเนื่องจากมีหลายองค์ประกอบที่มีรหัสtag1บนหน้า
Andrei Botalov

all(:xpath, '//a[text()="#tag1"]').first.clickคุณสามารถทำได้
Shuhei Kagawa

9

คุณสามารถมั่นใจได้ว่าคุณพบรายการแรกโดยใช้match:

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

แต่ที่สำคัญคุณอาจไม่ต้องการทำเช่นนี้เนื่องจากจะนำไปสู่การทดสอบที่เปราะบางซึ่งเพิกเฉยต่อกลิ่นรหัสเอาต์พุตที่ซ้ำกันซึ่งจะนำไปสู่ผลบวกที่ผิดพลาดซึ่งจะทำงานต่อไปเมื่อควรจะล้มเหลวเนื่องจากคุณลบการจับคู่ องค์ประกอบ แต่การทดสอบพบอีกอันอย่างมีความสุข

การเดิมพันที่ดีกว่าคือการใช้within:

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

สิ่งนี้ช่วยให้มั่นใจได้ว่าคุณจะพบองค์ประกอบที่คุณคาดหวังว่าจะพบในขณะที่ยังคงใช้ประโยชน์จากความสามารถในการรออัตโนมัติและการลองใหม่อัตโนมัติของ Capybara (ซึ่งคุณจะสูญเสียหากคุณใช้find('.selector').click) และทำให้ชัดเจนขึ้นมากว่าเจตนาคืออะไร


7

หากต้องการเพิ่มองค์ความรู้ที่มีอยู่ที่นี่:

สำหรับการทดสอบ JS Capybara ต้องเก็บเธรดสองเธรด (หนึ่งเธรดสำหรับ RSpec หนึ่งสำหรับ Rails) และกระบวนการที่สอง (เบราว์เซอร์) ในการซิงค์ ทำได้โดยการรอ (ถึงเวลารอสูงสุดที่กำหนดไว้) ในตัวจับคู่และวิธีการค้นหาโหนดส่วนใหญ่

Capybara Node#allนอกจากนี้ยังมีวิธีการที่ไม่ต้องรอเป็นหลัก การใช้มันก็เหมือนกับการบอกข้อมูลจำเพาะของคุณว่าคุณต้องการให้มันล้มเหลวเป็นระยะ ๆ

page.first('selector')คำตอบที่ได้รับการยอมรับให้เห็น นี้เป็นที่ไม่พึงประสงค์อย่างน้อยสำหรับรายละเอียด JS เพราะการใช้งานNode#firstNode#all

ที่กล่าวว่าNode#first จะรอถ้าคุณกำหนดค่า Capybara ดังนี้:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

ตัวเลือกนี้ถูกเพิ่มใน Capybara 2.5.0และเป็นเท็จโดยค่าเริ่มต้น

ดังที่ Andrei กล่าวถึงคุณควรใช้ไฟล์

find('selector', match: :first)

หรือเปลี่ยนตัวเลือกของคุณ ทั้งสองอย่างจะทำงานได้ดีโดยไม่คำนึงถึง config หรือไดรเวอร์

หากต้องการทำให้สิ่งต่าง ๆ ซับซ้อนยิ่งขึ้นใน Capybara เวอร์ชันเก่า (หรือเมื่อเปิดใช้งานตัวเลือกการกำหนดค่า) #findจะเพิกเฉยต่อความคลุมเครือและเพียงส่งคืนตัวเลือกการจับคู่แรก สิ่งนี้ก็ไม่ดีเช่นกันเนื่องจากทำให้ข้อกำหนดของคุณมีความชัดเจนน้อยลงซึ่งฉันคิดว่าทำไมถึงไม่เป็นพฤติกรรมเริ่มต้นอีกต่อไป ฉันจะไม่ระบุรายละเอียดเนื่องจากได้มีการกล่าวถึงข้างต้นแล้ว

แหล่งข้อมูลเพิ่มเติม:



2

เมื่อพิจารณาจากตัวเลือกทั้งหมดข้างต้นคุณสามารถลองสิ่งนี้ได้เช่นกัน

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

หากคุณใช้แตงกวาก็สามารถทำตามได้เช่นกัน

คุณสามารถส่งข้อความเป็นพารามิเตอร์จากขั้นตอนสถานการณ์ซึ่งอาจเป็นขั้นตอนทั่วไปเพื่อนำกลับมาใช้อีกครั้ง

สิ่งที่ต้องการ When a user clicks on "text" link

และในการกำหนดขั้นตอน When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

ด้วยวิธีนี้คุณสามารถใช้ขั้นตอนเดิมซ้ำได้โดยการย่อบรรทัดของโค้ดให้เล็กที่สุดและจะง่ายต่อการเขียนสถานการณ์แตงกวาใหม่


0

เพื่อหลีกเลี่ยงข้อผิดพลาดที่ไม่ชัดเจนในแตงกวา

แนวทางแก้ไข 1

first("#tag1").click

โซลูชันที่ 2

Cucumber features/filename.feature --guess
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.