ä¸å®å®ãã¹ããçã¿åºãCapybaraã調æãã
2020/06/12
@é座Rails #22
Masatoshi Iwasaki
èªå·±ç´¹ä»
Masatoshi Iwasaki
masa-iwasaki
masa_iwasaki
ããªã¼ã©ã³ã¹ã¨ã³ã¸ãã¢
ä»æ¥ã®ãã¼ã
Railsã§Capybaraã使ã£ããã¹ããä¸å®å®ãªã¨ãã«
ãã¨ãããããã試ãã¦ï¼ã
ã¨ããTipsã«ã¤ãã¦ã¾ã¨ãã¾ãã
ï¼ããããã£ã¦ãããï¼ãã¨ãããã¨ãå¤ããã...ï¼
対象ã¨ããç°å¢
話ããªããã¨
ç´æã§ããªããã¨
ï¼ä¸å¿ï¼Capybaraã¨ã¯
âCapybara is a library written in the Ruby programming language which makes it easy to simulate how a user interacts with your application.â
ä¸å®å®ãã¹ãã¨ã¯ï¼
ï¼æ¬çºè¡¨ã§ã®ï¼ä¸å®å®ãã¹ãã®å®ç¾©
ã»ãã¨ã¯éãï¼greenã«ãªãï¼ã¯ããªã®ã«ã
ãªããåå ä¸æã§è½ã¡ãï¼redã«ãªãï¼ãã¹ãã®ãã¨ã
ã¤ã¾ãããã¹ãèªä½ã¯æ£ããæ¸ãã¦ããï¼ã¯ãï¼åæã
ç¨ã«ãã»ãã¨ã¯è½ã¡ãã¹ãã ã£ãã®ã«ã»ã¨ãã©ã®ã±ã¼ã¹ã§éã£ã¦ãããã¨ããæ²ããäºå®ãçºè¦ãããã¨ããã
ä¸å®å®ãã¹ãã®çºçè¦å
ä¸å®å®ãã¹ãã®çºçè¦å
ä»æ¥ã®ã¡ã¤ã³ãããã¯
è½ã¡ã¥ãããã¹ããæ¸ããªãæ¹æ³
è½ã¡ã¥ãããã¹ããæ¸ããªãããã«
findã®å®è£
def find(*args, **options, &optional_filter_block)
options[:session_options] = session_options
synced_resolve Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
end
findã¡ã½ããã®ã³ã¼ãã¯ä»¥ä¸ã®ããã«ãªã£ã¦ããã
def synchronize(seconds = nil, errors: nil)
return yield if session.synchronized
seconds = session_options.default_max_wait_time if [nil, true].include? seconds
session.synchronized = true
timer = Capybara::Helpers.timer(expire_in: seconds)
begin
yield
rescue StandardError => e
session.raise_server_error!
raise e unless catch_error?(e, errors)
if driver.wait?
raise e if timer.expired?
sleep(0.01)
reload if session_options.automatic_reload
else
old_base = @base
reload if session_options.automatic_reload
raise e if old_base == @base
end
retry
ensure
session.synchronized = false
end
end
åã ã®è¦ç´ ã
追ã£ã¦ããã¨çµæ§è¤é
ç°¡ç¥å
begin
ã»ã¬ã¯ã¿ãæ¢ãã¯ã¨ãªã®å®è¡
rescue StandardError => e
raise e if ä¾å¤ãElementNotFound以å¤
raise e if å¶éæéè¶ ãã¦ãã
sleep(0.01)
retry
...
大çã«é¢ä¿ãªãå¦çãæãã¦ãã£ãããªæåãæ½åºããã¨ãããªæã
findã®æåã¾ã¨ã
è½ã¡ã¥ãããã¹ããæ¸ããªãããã«
findã®æåã¾ã¨ãï¼åæ²ï¼
findã®éãçªãããã±ã¼ã¹
1ï¼ã»ã¬ã¯ã¿ã®æå®ãä¸åå
# Bad:
expect(find(".user")["data-name"]).to eq("Joe")
# Good:
expect(page).to have_css(".user[data-name='Joe']")
Badã®ã³ã¼ãã§ã¯æåã«è¦ã¤ãã£ã .user ãªã¨ã¬ã¡ã³ããè¿ãã¦ããããdatasetãæå¾ ããå¤ã§ãªããã¨ãããã
ä¸è¨ã®ä¾ã¯ .user ãªè¦ç´ ããã¨ãã¨ããããã®è¦ç´ ã®dataå±æ§ã« name='Joe' ã追å ããããã¨ãæ³å®ãã¦ããã±ã¼ã¹ãJSã®å®è¡æéãé·ãããããã¯ãµã¼ãã¼ãµã¤ãã®ã¬ã¹ãã³ã¹ãé ããªã©ã®çç±ã§dataå±æ§ã¸ã®å¤æ´ãèµ·ããåã«ã¯ã¨ãªãå®è¡ããã¦ãã¾ãã¨ãã¹ãã失æããã
Goodã®ã³ã¼ãã使ãã°findãè¦ç´ ãè¦ã¤ããã¾ã§ä¸å®æéå¾ ã£ã¦ãããã
2ï¼findã¨ç°ãªãå¾ ã¡æ¹ãããfinderã¡ã½ãã
ãã®ãããã®å ãã¿
3ï¼å¶éæéãè¶ ããå®è¡æé
# spec/rails_helper.rb ãªã©ã§system specå ¨ä½ã§å¾ ã¡æéã延ã°ã
Capybara.default_max_wait_time = 5
# ç¹å®ã®è¦ç´ ã«ã¤ãã¦ç§æ°æå®ã§å¾ ã¤
find '.something', wait: 10
# ç¹ã«æ ¹æ ç¡ãå¾ ã¡æéã延ã°ãããå ´åã¯æ´æ°å¤ãæããã¨
# ãé©å½ã«å¢ããã¦ããã ãªãæãã§ããããããªã
find '.something-red', wait: Capybara.default_max_wait_time * 3
è½ã¡ã¥ãããã¹ããæ¸ããªãããã«
å人çãªä¸å®å®ãã¹ãã®é »åºä¾
ããã°è¨äºç»é²ã®system specã§ç»é²ãã¦ããDBã®ä¸èº«ããã§ãã¯ãããããªã±ã¼ã¹
# system specã®ä¸é¨
fill_in "ã¿ã¤ãã«", with: "CIä¸å®å®ã§åãªã"
click_on "ç»é²"
# ç»é²ãã¿ã³ãæ¼ããã¨ãã«éåæå¦çã ã¨BlogEntryã®è¿½å ãå®äºãã¦ããªãå¯è½æ§ããããã
# éçºç°å¢ã ã¨é«éã«å¦çãè¡ãããã®ã§å¤±æãããã¨ãã»ã¨ãã©ãªã
expect(BlogEntry.last.title).to eq "CIä¸å®å®ã§åãªã"
â»system specã§ActiveRecordã¤ã³ã¹ã¿ã³ã¹ã®å¤ããã§ãã¯ãããã¨ã®æ¯éã«è°è«ã¯ãããããããªãããæ¬çºè¡¨ã§ã¯å¯¾è±¡å¤ã¨ããã
ã¦ã¼ã¶ã¼ã¸ã®è¡¨ç¤ºãå¾ ã¤
flashãªã©ã§è¡¨ç¤ºãããã¡ãã»ã¼ã¸ãã¡ãã»ã¼ã¸ãå«ã¾ããè¦ç´ ãå¾ ã¤ãã¨ã§ãDBã«ãã¼ã¿ãããç¶æ ãä¿è¨¼ããã
fill_in "æ¬æ", "CIä¸å®å®ã§åãªã"
click_on "ç»é²"
expect(page).to have_css(".flash.notice")
expect(page).to have_content("ç»é²ãå®äºãã¾ãã")
expect(BlogEntry.last.body).to "CIä¸å®å®ã§åãªã"
çå
ã¦ã¼ã¶éç¥ãããæ¶ããï¼è¦ããªããªãï¼ã¨ãã大ä¸å¤«ï¼
findã®æåã¾ã¨ãï¼ã¾ãåºããªï¼ï¼
ã¦ã¼ã¶ã¼ã«å¯¾ãã確èªè¡¨ç¤ºãã²ããããã§ãã¯
ã»ã¬ã¯ã¿åãé«ãã¦ä¸å®å®ãã¹ããæ¸ãã
sleep() less
sleep()ã¯åç¾ææ³ã¨ãã¦ä½¿ã
wait_for_ajaxã¡ã½ããã«ã¤ãã¦
Seleniumã®è©³ç´°ãªåä½ãç¥ãããå ´å
Selenium::Webdriver::Loggerãç°å¢å¤æ°ã®DEBUGãè¦ã¦æ¨æºåºåã«è©³ç´°ãã°ãåºãã¦ãããï¼ï¼ï¼ï¼
ããã¯...
DEBUG=true bin/rspec path/to/some_spec.rb
ï¼ãã¾ãï¼
ä¸å®å®ãã¹ã対çã®ä¾¿å©è¨å®
ä¸å®å®ãã¹ãã®é²æ¢ã»æ¤åºã«å½¹ç«ã¤è¨å®
ç¹ã«CIç°å¢ã§ã¯ãã£ã¦ããããã
CSSã¢ãã¡ã¼ã·ã§ã³ãåã
CSSã¢ãã¡ã¼ã·ã§ã³ã®å®è¡é度ãå½±é¿ãã¦ãã¹ãå®è¡çµæãä¸å®å®ã«ãªããã¨ãé²æ¢ãã
# in spec/rails_helper.rb
Capybara.disable_animation = true
Capybara::Server::AnimationDisabler ã§HTMLãããã¼ã«ã¢ãã¡ã¼ã·ã§ã³ãæ¢ããã¹ã¿ã¤ã«æå®ãåãè¾¼ãã§ããã
ãã®è¨å®ã§ã¯ä¸ååã¨ããå ´åã¯ä¸è¨è¨å®ã使ãããèªåã§ãã¹ãå®è¡æã«ä½¿ãã¢ãã¡ã¼ã·ã§ã³ç¡å¹åããããã®CSSãç¨æãã¦testå®è¡æã ããããèªã¿è¾¼ãããã«ããã°è¯ãã
assets:precompileãå ã«èµ°ããã
# CIä¸ã§ bin/rspec å®è¡åã«èµ°ããã
RAILS_ENV=test bin/rails assets:precompile
æåã®system specãå®è¡ãããåã«assets:precompileãèµ°ããã¦ãããååã«å®è¡ããããã¹ãã§ã®ãã¼ãæé延é·ãªã©ã«ããä¸å®å®ãªçµæãã¿ã¤ã ã¢ã¦ããé²ãã
CIä¸ã§assets:precompileãå®è¡ãããããstaging/productionç°å¢ã«ãããã¤ããåã®åä½ç¢ºèªã«ããªãã
ï¼ãªãã·ã§ã³ï¼
config.assets.compileã®ç¡å¹å
# in config/environments/test.rb
if ENV[âCIâ]
config.assets.compile = false
end
å ãã¦ãconfig.assets.compileãç¡å¹åãããã¨ã§assets:precompileããå¾ã§åç §ã§ããªãassetsãããã¨ãã®ã¾ã¾ãã¹ããè½ã¡ã¦ãããã
ãã©ã¦ã¶ã³ã³ã½ã¼ã«ã®ãã°ããã§ãã¯ãã
capybara-chromedriver-logger
READMEã«ããç»åï¼ä»¥ä¸ï¼ã®ããã«ãã©ã¦ã¶ã®ã³ã³ã½ã¼ã«ã«è¡¨ç¤ºããããã°ãåºãã
ããããcapybara-chromedriver-logger ã使ã£ãå ´åã®tipsãç´¹ä»ã使ããªãå ´åã§ããã°åºåããå ´åã¯å ±éãã話ã®ã¯ãã
ãã°ã®ãã¤ãºãæ¸ãã
Vue.config.devtools = false;
Vue.config.productionTip = false;
éç¨ä¸ãé常ã¯ä¸åãã°ãã§ãªããã¨ãåæã¨ãã¦ããã°ãåºãããã¹ã¦failãããããã«ããªãã¨ãCIã®å®è¡æãã°ã追ããªãã¨ãããªããããæ°ã¥ãã¥ããã
ãã¨ãã°Vue.jsã ã¨DevToolsãproductionåãã®tipsãªã©ã表示ãããã®ã§ãassets:precompileã®æç¹ãªã©ã§è¡¨ç¤ºããªãããã«ãã¦ããããã
filterãæ´»ç¨ãã
# spec/rails_helper.rb
Capybara::Chromedriver::Logger.filters = [
/the server responded with a status of 422/
]
JSã©ã¤ãã©ãªãåºããã°ãæå¶ãããªãã·ã§ã³ããªãã£ãããHTTPã¹ãã¼ã¿ã¹ã³ã¼ãã§4xxãè¿ã£ã¦ããã±ã¼ã¹çã§ã¯ãã°ãåºã¦ãã¾ãã®ã§filterã使ã£ã¦æå¶ããã
rspecã®exampleæ¯ã«å¶å¾¡ãããä»çµã¿ãä½ãå ´åã¯before/after hooksãçµã¿åããããã¨è¿½å ã®æéããããã
æ¬æ¥ã®ã¾ã¨ã
ä¸å®å®ãã¹ãã®é²æ¢ã»æ¤åºã«å½¹ç«ã¤è¨å®
è½ã¡ã¥ãããã¹ããæ¸ããªãããã®ãã¤ã³ã
æå¾ã«
æ¬æ¥ã®ã¿ã¤ãã«
ä¸å®å®ãã¹ããçã¿åºãCapybaraã調æãã
ðâ
Capybaraå ¨ç¶æªããªãï¼ï¼
調æãå¿ è¦ãªã®ã¯æã ã ã£ã
https://www.flickr.com/photos/89654772@N05/8157292018 å å·¥æ¸ã¿
ãæ¸ è´ãããã¨ããããã¾ãã