ããã«ã¡ã¯ãMedPeerã®éçºãæ å½ãã¦ãã森ç°ã§ãã ä»åã¯ç§ãéçºã«åç»ãã¦ããMedPeerã«å ã E2Eãã¹ãã§å©ç¨ãã¦ããCapybaraã¨ãreg-cliãå©ç¨ãã¦ãã¸ã¥ã¢ã«ãªã°ã¬ãã·ã§ã³ãã¹ã(以ä¸VRT)ãè¡ããç°å¢ãæ´åããã®ã§ãããã«ã¤ãã¦ãç´¹ä»ããã¦ããã ãã¾ãã
- ãªããVRTãå°å ¥ããã®ãï¼
- VRTã®è¦ä»¶ã¨æè¡é¸å®
- å®éã«æ§ç¯ããVRTåºç¤ã®æ¦è¦
- VRTåºç¤ã®å ·ä½çãªè©±
- ãããã«
- åèã«ããã¦é ããè³æ
ãªããVRTãå°å ¥ããã®ãï¼
MedPeerã§ã¯å ã System Specãæ´»ç¨ããE2Eãã¹ããå©ç¨ãã¦ããã³ãã¨ã³ããå«ãã¦å質ãæ ä¿ãã¦ããã¾ãããããã¶ã¤ã³å´©ãã®å½±é¿ãæ¤ç¥ããã®ã¯é£ãããè¦æ¨¡ã®å¤§ããå¤æ´ãè¡ãéã«ã¯æåã§ã®ç»é¢ç¢ºèªãè¡ã£ã¦ããã¾ããã
ããããæåã§ã®ç»é¢ç¢ºèªã¯æ¤è¨¼ã³ã¹ããé«ããéçºä¸ã®ããã«ããã¯ã«ãªããã¡ã§ãã£ãã®ã¨ãæåã§ã®æ¤è¨¼ã¯æ¬æ¥ã§ããã°æ¤åºããã¦ã»ããå½±é¿ãæ¤ç¥ã§ããã«ãªãªã¼ã¹ããã¦ãã¾ããã¨ããããæ¤è¨¼ç²¾åº¦ãæ ä¿ãã¤ã¤ã¹ãã¼ãæãæã£ãéçºãå®ç¾ããä¸ã§èª²é¡ã¨ãªã£ã¦ããã¾ããã
ãããªä¸ã§ãVRTãæ¢åã®CIã«çµã¿è¾¼ã¿ããã¶ã¤ã³å´©ããæ¤ç¥ã§ããã°ãåè¿°ã®èª²é¡ã®è§£æ±ºã«å¯ä¸ã§ããã®ã§ã¯ï¼ã¨æã£ãã®ãå°å ¥ã®èæ¯ã§ãã
VRTã®è¦ä»¶ã¨æè¡é¸å®
ä¸è¿°ã®éããæ¤è¨¼ç²¾åº¦ãæ ä¿ãã¤ã¤ã¹ãã¼ãæãæã£ãéçºã«å¯ä¸ããããã«VRTãå°å ¥ããã«ããã£ã¦ãå¿ è¦ãªè¦ä»¶ã以ä¸ã¨èãã¾ããã
- ã³ã¹ããæããã«VRTãè¨è¿°ã»çµã¿è¾¼ããããã«ãããã¨
- å¹ åºãVRTãè¨è¿°ããå¿ è¦ãããã®ã§ãè¨è¿°ã®ãã¼ãã«ããªãã¹ãä¸ããããã«æ¢åã®ä»çµã¿ã®å»¶é·ç·ä¸ã§å®ç¾ãã
- ã¡ã¤ã³ãã©ã³ãã¸ã®ãã¼ã¸åã«ãã¶ã¤ã³å´©ãã®çºçã«æ°ã¥ãä¿®æ£ã§ãããã¨
- ãã¶ã¤ã³ã¸ã®å½±é¿ãPRã®statusã§å¤æã§ããCIãè½ã¨ããã¨ã§ãã¼ã¸åã«æ°ã¥ãã¦å¯¾å¿ã§ããããã«ãã
ãã®è¦ä»¶ã«åããã¦ãMedPeerã§ã¯Capybaraã¨reg-cliã使ã£ã¦æ§ç¯ãããã¨ã«ãã¾ããã
ãã§ã«System Specã使ã£ã¦è¡ãªã£ã¦ããE2Eãã¹ãã§å©ç¨ãã¦ããCapybaraã®æ©è½ã§ããCapybara::Session#save_screenshot
ã使ã£ã¦ä»»æã®ã¿ã¤ãã³ã°ã§ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ãããã¼ã«ã«ã§ãå®è¡ã§ããreg-cliã使ã£ã¦åå¾ããã¹ã¯ãªã¼ã³ã·ã§ããã®å·®åãæ¤ç¥ãããã¨ã§ãæ¢åã®ä»çµã¿ãæ´»ããã³ã¹ããæããã«CIã§ãã¶ã¤ã³å´©ãããã¼ã¸åã«æ¤ç¥ã§ããã®ã§ã¯ãªããã¨èãã¾ããã
å®éã«æ§ç¯ããVRTåºç¤ã®æ¦è¦
æ§ç¯ããVRTåºç¤ã®æ¦è¦ã以ä¸ã®éãã§ãã
ã¾ãäºåä½æ¥ã¨ãã¦spec/systems/visual_regression/screenshots/master
ã«æ£ã¨ãªãç¾æç¹ã§ã®ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ããSystem Specãä½æããã¡ã¤ã³ãã©ã³ãã«é
ç½®ãã¦ããã¾ãã
ããã¦å®éã«PRãä½æãããéã«ãPRã®ãã©ã³ãã«ã¦è¿½å ããSystem Specãå®è¡ããCIä¸ã®PRã®ãã©ã³ãã§åå¾ããã¹ã¯ãªã¼ã³ã·ã§ãããspec/systems/visual_regression/screenshots/compare
ã«é
ç½®ãã¾ãã
ããã¦ãreg-cli
ã使ã£ã¦ããããã®ãã£ã¬ã¯ããªã«é
ç½®ãããåä¸ãã¹ã»å称ã®ãã¡ã¤ã«ã®å·®åããã§ãã¯ããå·®åãããã°CIã失æãããããã«ãã¦ãã¾ãã
æåæ
失ææ
VRTåºç¤ã®å ·ä½çãªè©±
System Specå ã§ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ãã
System Specå ã§Capybaraã使ã£ã¦VRTç¨ã®ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ã§ããããã«ä»¥ä¸ã®Helperãç¨æãã¾ããã
module VrtScreenshotHelper VRT_SCREENSHOT_BASE_PATH = 'spec/system/visual_regression/screenshots' def vrt_screenshot(page, path:, full: true) return unless screenshot_enabled? target = update_master? ? 'master' : 'compare' base_path = screenshot_base_path(target: target) if full save_full_size_screenshot(page, base_path.join(path)) else page.save_screenshot(base_path.join(path)) end end private def save_full_size_screenshot(page, path) original_size = Capybara.current_session.driver.browser.manage.window.size resize_window_to_fit_page page.save_screenshot(path) reset_window_size(original_size.width, original_size.height) end def screenshot_base_path(target:) Rails.root.join(VRT_SCREENSHOT_BASE_PATH, target) end def screenshot_enabled? ENV["VRT_SCREENSHOT_ENABLE"] != "false" end def update_master? ENV["VRT_SCREENSHOT_UPDATE_MASTER"] == "true" end # NOTE: ãã«ãµã¤ãºã®ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ããããã«ã¦ã£ã³ãã¦ãµã¤ãºããã¼ã¸ã«åããã def resize_window_to_fit_page width = Capybara.page.execute_script(<<~JS) return window.outerWidth JS height = Capybara.page.execute_script(<<~JS) return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight); JS reset_window_size(width, height) end def reset_window_size(width, height) Capybara.current_session.driver.browser.manage.window.resize_to(width, height) end end
Helperå
ã«å®è£
ãã¦ããvrt_screenshot
ã使ããã¨ã§å©ç¨è
å´ã§ä»¥ä¸ã®è¨å®ãè¡ãVRTç¨ã®ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ã§ããããã«ãã¦ãã¾ãã
- ã¹ã¯ãªã¼ã³ã·ã§ãããé
ç½®ãããã£ã¬ã¯ããª
- reg-cliã§å·®åãã§ãã¯ãè¡ããã£ã¬ã¯ããª
spec/system/visual_regression/screenshots
ãèªåè¨å® - æ£ã¨ãªãç»åãæ´æ°ããå ´åã«ã¯èªåçã«
master
ã«é ç½®ãã
- reg-cliã§å·®åãã§ãã¯ãè¡ããã£ã¬ã¯ããª
- ãã«ãµã¤ãºã§ã®ã¹ã¯ãªã¼ã³ã·ã§ããã®åå¾
- ã¹ã¯ãªã¼ã³ã·ã§ããåå¾æã«ç»é¢ãµã¤ãºããã«ãµã¤ãºã«å¤æ´ãã¦ãããã¼ã¸å ¨ä½ã®ã¹ã¯ãªã¼ã³ã·ã§ãããåå¾ãã
ãã®ãã«ãã¼ã使ã£ã¦å©ç¨ããå´ã¯ä»¥ä¸ã®ãããªå½¢ã§ãã«ãµã¤ãºã®ã¹ã¯ãªã¼ã³ã·ã§ãããå·®åãã§ãã¯ãããã£ã¬ã¯ããª(spec/system/visual_regression/screenshots/compare/service_name/root_page.png
or spec/system/visual_regression/screenshots/master/service_name/root_page.png
)ã«èªåé
ç½®ã§ããããã«ãã¾ãã ð¸
require 'support/vrt_screenshot_helper' RSpec.describe 'Service name', :js do include VrtScreenshotHelper it 'sample vrt' do visit root_path expect(page).to have_css '.sample-selector' # NOTE: ãã¼ã¸ãä¸å®è¡¨ç¤ºãããã®ãå¾ ã¤ vrt_screenshot(page, path: "service_name/root_page.png") end end
reg-cliã§ã¹ã¯ãªã¼ã³ã·ã§ããã®å·®åããã§ãã¯ãã
reg-cliã使ã£ã以ä¸ã®ã¹ã¯ãªãããpackage.json
ã«è¨å®ãäºåä½æ¥ã§åå¾ãã¦ããæ£ã¨ãªãç»åã¨CI(ã¾ãã¯ãã¼ã«ã«ã§ã)ä¸ã§åå¾ããç»åãæ¯è¼ãã¦5%以ä¸ã®å·®åããã£ãå ´åã«ã¨ã©ã¼ã«ããããã«ãã¦ãã¾ãðµï¸
{ "scripts": { "test:vrt": "reg-cli spec/system/visual_regression/screenshots/compare spec/system/visual_regression/screenshots/master spec/system/visual_regression/screenshots/diff -R spec/system/visual_regression/screenshots/diff/report.html -J spec/system/visual_regression/screenshots/diff/reg.json -T 0.05",
å®è¡ãã¦ããã¹ã¯ãªããã®è©³ç´°ã¯ä»¥ä¸ã®éãã§ããæå®ã§ãããªãã·ã§ã³ã®è©³ç´°ã¯å ¬å¼ã®READMEãã確èªããã ããã°ã¨æãã¾ãã
$ yarn run reg-cli \ spec/system/visual_regression/screenshots/compare \ # ãã§ãã¯å¯¾è±¡ã®ã¹ã¯ãªã¼ã³ã·ã§ããã®é ç½®å spec/system/visual_regression/screenshots/master \ # æ£ã¨ããã¹ã¯ãªã¼ã³ã·ã§ããã®é ç½®å spec/system/visual_regression/screenshots/diff \ # å·®åã表ãç»åã®åºåå -R spec/system/visual_regression/screenshots/diff/report.html \ # å·®åã¬ãã¼ãã®åºåå -J spec/system/visual_regression/screenshots/diff/reg.json \ # å·®åã¬ãã¼ã(JSON)ã®åºåå -T 0.05 # 許容ããå·®åã®é¾å¤(%)
å®éã®å®è¡çµæã¯ä»¥ä¸ã®ããã«ç¢ºèªãããã¨ãã§ããå·®åããã£ãéã«ã¯exit code 1.
ã¨ãªãCIã失æãã¾ãð
yarn run v1.22.22 $ reg-cli spec/system/visual_regression/screenshots/compare spec/system/visual_regression/screenshots/master spec/system/visual_regression/screenshots/diff -R spec/system/visual_regression/screenshots/diff/report.html -J spec/system/visual_regression/screenshots/diff/reg.json -T 0.05 â pass spec/system/visual_regression/screenshots/compare/service_name/root_page.png â change spec/system/visual_regression/screenshots/compare/service_name/sub_page.png â 1 file(s) changed. â 1 file(s) passed. Inspect your code changes, re-run with `-U` to update them. error Command failed with exit code 1.
åãããããã³ãã³ãã§VRTãå®è¡ã§ããããã«ãã
åè¿°ã¾ã§ã®æé ã«ã¦ãCIã§System Specãå®è¡ãã¹ã¯ãªã¼ã³ã·ã§ãããåå¾å¾ã«reg-cliã§ã®å·®åãã§ãã¯ã®ã¹ã¯ãªãããå®è¡ããã°ãä¸å®VRTã¨ãã¦æ©è½ããããã«ãªã£ããã¨æãã¾ãð
ããããVRTå®è¡ã®ããã«è¤æ°ã®ã¹ã¯ãªãããæåã§å®è¡ããã®ã¯æéã«æããã®ã§ã以ä¸ã®ãããªRakeã¿ã¹ã¯ãç¨æãã¦bin/rails visual_regression:run
ã§System Specã«ããã¹ã¯ãªã¼ã³ã·ã§ããã®åå¾ãreg-cli
ã«ããç»åæ¯è¼ãå®è¡ããããã«ãã¾ããã
require 'optparse' namespace :visual_regression do desc 'Run visual regression tests' task run: :environment do options = {} option_parser = OptionParser.new do |parser| parser.banner = 'Usage: rake visual_regression:run [options]' parser.on('-t', '--target TARGET', 'The directory to run the tests (default: spec/system/visual_regression)') do |v| options[:target] = v end parser.on('-u', '--update', 'Update the master screenshots (default: false)') do |_v| options[:update] = true end parser.on('-h', '--help', 'Show Help') do |v| options[:help] = v puts option_parser.help exit end end # NOTE: OptionParser#order! 㯠optionã«åå¨ããªãå¤ãããã¨ãã¼ã¹ãä¸æãã¦ãã¾ãã®ã§ã # rake taskã§å©ç¨ããå ´åã«æå®ããã³ãã³ãåã¨ãªãã·ã§ã³ã®ã»ãã¬ã¼ã¿ã¼`--`ãåé¤ãã # https://docs.ruby-lang.org/ja/latest/class/OptionParser.html#I_PARSE--21 option_parser.parse(ARGV - ["visual_regression:run", "--"]) options[:target] ||= 'spec/system/visual_regression' options[:update] ||= 'false' env = { 'VRT_SCREENSHOT_ENABLE' => 'true', 'VRT_SCREENSHOT_UPDATE_MASTER' => options[:update].to_s, } rspec_success = system(env, 'bin/rspec', options[:target]) # System Specã«ããã¹ã¯ãªã¼ã³ã·ã§ããã®åå¾ raise "Get ScreenShot command failed with exit code #{$CHILD_STATUS.exitstatus}" unless rspec_success vrt_success = system('yarn', 'run', 'test:vrt') # reg-cliã«ããã¹ã¯ãªã¼ã³ã·ã§ããã®å·®åæ¯è¼ raise "Check Image diff Command failed with exit code #{$CHILD_STATUS.exitstatus}" unless vrt_success end end
ç¹å®ã®VRTã®æ£ã¨ãªãç»åãã¡ã¤ã«ãæ´æ°ããéã«ã以ä¸ã®ã³ãã³ãã§æ´æ°ã§ããããã«ãã¾ããã
$ bin/rails visual_regression:run -- -u -t spec/visual_regression/your_test_spec.rb
CIã§å·®åããã§ãã¯ãã
MedPeerã§ã¯CircleCIãå©ç¨ãã¦ããã®ã§å ã»ã©ã®Rakeã¿ã¹ã¯ãå®è¡ããçµæãã¢ã¼ãã£ãã¡ã¯ãã«ã¢ãããã¼ããããããªstepãè¨å®ãããã¨ã§CIä¸ã§VRTãå®è¡ããããã«ãã¦ãã¾ãã
visual_regression: steps: - run: name: run visual regression command: bin/rails visual_regression:run - store_artifacts: path: spec/system/visual_regression/screenshots
CircleCIã®ã¢ã¼ãã£ãã¡ã¯ãã¯ãã©ã¦ã¶ä¸ã§é²è¦§ã§ãããããreg-cli
ã§ä½æããhtmlã¬ãã¼ãã以ä¸ã®ããã«ããã®ã¾ã¾ãã©ã¦ã¶ã§è¡¨ç¤ºãã¦å·®åã®è©³ç´°ã確èªãããã¨ãã§ãã¦é常ã«ä¾¿å©ã§ãã ð
https://github.com/reg-viz/reg-cli/tree/main?tab=readme-ov-file#html-report
OSéã§ã®å©ç¨ãã©ã³ãã«ããéããå¸åãã
å½æMedPeerã§ã¯font-family
ã®æå®ã以ä¸ã®ãããªã¦ã¼ã¶ã¼ã®OSãã©ã³ããå°éãããããªãã©ã³ãæå®ã«ãªã£ã¦ããã¾ããã
font-family: system-ui, sans-serif;
éçºç°å¢ã¯Debianç³»ã®OSã¤ã¡ã¼ã¸ãå©ç¨ãã¦ãã¾ãããCIã§ã¯Ubuntuç³»ã®ã¤ã¡ã¼ã¸ã使ç¨ãã¦ãããå®è¡ç°å¢ã«ãã£ã¦é©ç¨ããããã©ã³ããå¤ãã£ã¦ãã¾ããã¨ã§ããã¼ã«ã«ã§äºåã«åå¾ããæ£ã¨ãªãã¹ã¯ãªã¼ã³ã·ã§ããã¨CIã§åå¾ããã¹ã¯ãªã¼ã³ã·ã§ãããæ¯è¼ããç¾ç¶ã®æ¹å¼ã§ã¯ãOSã«ãã£ã¦é©ç¨ããããã©ã³ããç°ãªãããå·®åãçºçãã¦ãã¾ãã¾ããã
ãããåå ã§VRTã失æãã¦ãã¾ããã¨ãå¤ãã£ãã®ã§ã以ä¸ã®ããã«VRTå®è¡æã«ã®ã¿true
ã¨ãªãã«ã¹ã¿ã ã³ã³ãã£ã°ãè¨å®ãã¦ã
Rails.application.configure do # NOTE: VRTã®ã¹ã¯ãªã¼ã³ã·ã§ããåå¾ãå¤å¥ããããã®ã«ã¹ã¿ã è¨å® screenshot_enable = ENV["VRT_SCREENSHOT_ENABLE"] == "true" config.x.visual_regression.screenshot_enabled = Rails.env.test? && screenshot_enable end
以ä¸ã®ãããªVRTç¨ã®Webãã©ã³ããé©ç¨ããCSSãç¨æãã
@import "https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap"; body { font-family: "Noto Sans JP", sans-serif !important; }
VRTå®è¡æã ãèªã¿è¾¼ããã¨ã§ OS éã®ãã©ã³ãå·®åãç¡è¦ã§ããããã«ãã¾ããã
<% if Rails.configuration.x.visual_regression.screenshot_enabled %> <%= stylesheet_pack_tag 'visual_regression/override' %> <% end %>
ãããã«
ã¾ã æ¡å éä¸ã®ããå ·ä½çãªå¹æã¾ã§ã¯æ¤è¨¼ã§ãã¦ãã¾ããããMedPeerã«VRTåºç¤ãæ§ç¯ãããã¨ã«ãã£ã¦ãæåãã¹ãã®åæ¸ãCSSãªãã¡ã¯ã¿ãªã³ã°ãå®å ¨ã«è¡ãããã®ç°å¢ãæ´åã§ããããã«ãªãã¾ããð
MedPeerã¯å»çãæ±ããµã¼ãã¹ã®ããããããã£ãä»çµã¿ãå©ç¨ãã¦å®å®çãªãµã¼ãã¹æä¾ãå®ç¾ãã¤ã¤ãã¹ãã¼ãæãç¶æãã¦ããããã§ãðª
æå¾ã¾ã§èªãã§ããã ããããã¨ããããã¾ããâ¨
åèã«ããã¦é ããè³æ
æ¯éèªè ã«ãªã£ã¦ãã ããï¼
ã¡ããã¢ã§ã¯ä¸ç·ã«åã仲éãåéãã¦ãã¾ãã
ãå¿åããå¾
ã¡ãã¦ããã¾ãï¼
â åéãã¸ã·ã§ã³ã¯ãã¡ã medpeer.co.jp
â ã¨ã³ã¸ãã¢ç´¹ä»ãã¼ã¸ã¯ãã¡ã engineer.medpeer.co.jp
â ã¡ããã¢å ¬å¼YouTubeã www.youtube.com
â ã¡ããã¢å
¬å¼note
style.medpeer.co.jp