Ruby Nation 2017: Building the New Rails System Test Framework

Ruby Nation 2017: Building the New Rails System Test Framework

At the 2014 RailsConf DHH declared system testing would be added to Rails. Three years later, Rails 5.1 makes good on that promise by introducing a new testing framework: ActionDispatch::SystemTestCase. The feature brings system testing to Rails with zero application configuration by adding Capybara integration. After a demonstration of the new framework, we'll walk through what's uniquely involved with building OSS features & how the architecture follows the Rails Doctrine. We'll take a rare look at what it takes to build a major feature for Rails, including goals, design decisions, & roadblocks.

C44e1f7e22c3f23cff7bc130871047ef?s=128

Eileen M. Uchitelle

June 16, 2017
Tweet

Transcript

  1. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING THE NEW RAILS System Tests
  2. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run Hi! I’m Eileen M. Uchitelle
  3. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run I’m a Systems Engineer @
  4. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run I’m on the Core Team
  5. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run You can find me online @eileencodes
  6. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING THE NEW RAILS System Tests
  7. At the 2014 RailsConf…

  8. None
  9. IS DEAD

  10. Today we do [Rails does] nothing to encourage full system

    tests. There's no default answer in the stack. That's a mistake we’re going to fix. “ —DHH in “TDD is dead. Long live testing.” http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
  11. 3 years later…

  12. Zomg, Rails 5.1 has system testing!!1!11!

  13. Wait, what’s a system test?

  14. Tests your application as a system

  15. gem 'capybara', '> 2.13.0' gem 'selenium-webdriver' Rails 5.1 Gemfile

  16. application_system_test_case.rb require 'test_helper' class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using:

    :chrome, screen_size: [1400, 1400] end
  17. require "application_system_test_case" class PostsTest < ApplicationSystemTestCase test "visiting the index"

    do visit posts_url assert_selector "h1", text: "Post" end end
  18. rails test:system Running System Tests

  19. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Why did it take 3 years?
  20. Integration tests were slowww….

  21. None
  22. None
  23. Yo, can you make my system test promise come true?

  24. I had never used Capybara before

  25. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Guiding Principles
  26. 1. Optimize for programmer happiness 2. Convention over configuration 3.

    The menu is omakase 4. No one paradigm 5. Exalt beautiful code 6. Provide sharp knives 7. Value integrated systems 8. Progress over stability 9. Push up a big tent The Rails Doctrine
  27. Optimize for programmer happiness

  28. require 'test_helper' # set the driver Capybara.current_driver = :selenium #

    register the driver & browser Capybara.register_driver :selenium do |app| Capybara::Selenium::Driver.new(app, browser: :chrome).tap do |driver| driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*[1400, 1400]) end end # set the server Capybara.server = :puma Capybara.always_include_port = true # register the server Capybara.register_server :puma do |app, port, host| Rack::Handler::Puma.run(app, Port: port, Threads: "0:1") end
  29. There is zero configuration required to use Capybara with Rails

    5.1
  30. require 'test_helper' class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome,

    screen_size: [1400, 1400] end
  31. Value integrated systems

  32. Rails as a whole now addresses system testing

  33. Progress over stability

  34. When system tests were merged there were a few known

    bugs
  35. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Implementation & Architecture
  36. Config defaults: Why Selenium?

  37. Can see the tests running in the browser

  38. Better for beginners learning Rails and Capybara

  39. driven_by :selenium Driver Options

  40. driven_by :poltergeist Driver Options

  41. Config defaults: Why Chrome?

  42. Chrome is widely used

  43. For awhile, Firefox was broken with Selenium

  44. driven_by :selenium, using: :chrome Driver Options

  45. driven_by :selenium, using: :firefox Driver Options

  46. Driver Options driven_by :selenium, using: :firefox
 screen_size: [1400, 1400]

  47. Driver Options driven_by :selenium, using: :firefox
 options: { url: "http://chrome:4444/hub"

    }
  48. Config defaults: Failure Screenshots

  49. def after_teardown take_failed_screenshot Capybara.reset_sessions! super end Driver Options

  50. test "the page contains users" do visit users_path take_screenshot assert_selector

    "h1", text: "Arya" end Take a Screenshot
  51. Database Cleaner not required

  52. Test transaction opens a connection

  53. Puma opens a second connection Test transaction opens a connection

  54. The first connection can’t see the data inserted on the

    second connection
  55. None
  56. I asked for help from @tenderlove & @matthewd

  57. Test transaction opens a connection

  58. Puma connects on the same connection Test transaction opens a

    connection
  59. Plumbing

  60. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  61. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  62. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run end […]
  63. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}) @driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size) end driven_by :selenium end SystemTestCase.start_application end
  64. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def initialize(name, **options) @name = name @browser = options[:using] @screen_size = options[:screen_size] @options = options[:options] end def use register if selenium? setup end
  65. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  66. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def use register unless rack_test? setup end private def rack_test? @name == :rack_test end […]
  67. # In action_dispatch/system_testing/driver.rb def register Capybara.register_driver @name do |app| case

    @name when :selenium then register_selenium(app) when :poltergeist then register_poltergeist(app) when :webkit then register_webkit(app) end end end
  68. # In action_dispatch/system_testing/driver.rb def register Capybara.register_driver @name do |app| Capybara::Selenium::Driver.new(app,

    { browser: @browser }.merge(@options)) .tap do |driver| driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) end end end
  69. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def use register if selenium? setup end private def selenium? @name == :selenium end […]
  70. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: private def setup Capybara.current_driver = @name end end end end
  71. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING Open Source Features
  72. Public Work

  73. Feels like everyone is judging you

  74. None
  75. Stakeholders

  76. None
  77. None
  78. $%&' ()*+ ,-./

  79. $%&' ()*+ ,-./ 70

  80. $%&' ()*+ ,-./

  81. $%&' ()*+ ,-./ Perfect?

  82. Consensus is the enemy of vision

  83. Dealing with multiple stakeholders

  84. Manage expectations

  85. Know who you are building the feature for

  86. Be open to change

  87. Open source doesn’t work without contributors

  88. Build a foundation for others to work off of

  89. Thanks @twapole

  90. Thanks @robin850

  91. Thanks @mtsmfm & @lucasmazza

  92. Thanks @renchap

  93. Open source doesn’t work without contributors

  94. Open source doesn’t work without you

  95. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run Thank you Ruby Nation!
  96. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run Find me everywhere @eileencodes