Using Capybara to test responsive code
Tags: ruby, rails, testing, mobile
Published: 2022-11-11 20:41:21

As more users opt for mobile browsing, [responsive design](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design) becomes more important — even for applications that are primarily used on a desktop. Responsive design is a key consideration for Aha! Ideas because it's convenient to answer polls and submit ideas using mobile devices. Usually the application behavior between mobile and desktop are the same, just rearranged on the page. But what happens when application behavior is different between devices with different-sized screens? Automated tests are necessary to ensure these different screen behaviors perform as expected in their environments.

Engineering at Aha! focuses on using and improving [the Capybara test framework](https://github.com/teamcapybara/capybara). We have added many helpers and additional functionality to make working with Capybara easy. Testing at mobile widths is another chance to improve our testing tooling. Here is the incremental approach that we used to add mobile testing helpers.

## First pass

Below, we have an example test.

```ruby
context "mobile user" do
  scenario "user logs in on mobile" do
    # These labels are specific to mobile widths
    expect(page).to have_content("First name")
    expect(page).to have_content("Last name")
  end
end
```

This test will fail because we aren't resizing to mobile width, so our name labels won't appear. Let's fix that.

```ruby
context "mobile user" do
  # resize to mobile width
  before { page.driver.browser.manage.window.resize_to(375, 764) }

  scenario "user logs in on mobile" do
    # These labels are specific to mobile widths
    expect(page).to have_content("First name")
    expect(page).to have_content("Last name")
  end
end
```

This is an acceptable first pass. Our page is indeed resized for mobile testing. However, it presents three problems:

1. It doesn't tell us why we are resizing. The test has that information but it would not be clear out of the test.
2. This code isn't particularly reusable because it requires some insight into how Capybara works.
3. The test window remains resized for all subsequent tests. This is a subtle problem that will lead to unexpected and intermittent test failures.

## Using a helper

Let's see how we can fix those three problems. Below is a helper we can use to resize for mobile testing.

```ruby
# in spec/support/capybara_helpers.rb
def with_mobile_width
  page.driver.browser.manage.window.resize_to(375, 764) # resize to mobile
  yield # run the test
  page.driver.browser.manage.window.resize_to(1920, 1080) # resize back to default
end
```

And then in our test:

```ruby
context "mobile user" do
  scenario "user logs in on mobile" do
    with_mobile_width do
       # These labels are specific to mobile widths
       expect(page).to have_content("First name")
       expect(page).to have_content("Last name")
    end
  end
end
```

This solution works better. It can be reused, it tells us why we are resizing, and it won't interfere with other tests. But there is still room for improvement.

We are adding another layering of nesting to the test. If we continue to use it in other places, then we are going to have some deeply nested tests. There is another way we can get this functionality without using this helper.

## Updating our configuration

I want to be able to drop a `:mobile_width` in our test declaration. This resolves our three problems and won't clutter the tests. Below is an update to our configuration.

```ruby
# in spec/support/capybara.rb
rspec_config.before(:each) do |example|
  browser = Capybara.current_session.driver.browser

  # if we have :mobile in test, resize. Otherwise go to default.
  if example.metadata.key?(:mobile_width)
    browser.manage.window.resize_to(375, 764)
  else
    browser.manage.window.resize_to(1920, 1080)
  end
end
```

Here we are using the `before(:each)` hook. This does exactly what it says on the box. Before each test runs, hook into it and perform an action. In this case, we are checking to see if the test has a `:mobile_width` key. If it does, run at a mobile width. If it does not, run at the standard width. The test looks a lot cleaner.

```ruby
context "mobile user", :mobile_width do
  scenario "user logs in on mobile" do
    # These labels are specific to mobile widths
    expect(page).to have_content("First name")
    expect(page).to have_content("Last name")
  end
end
```

All the tests in the mobile user context are now going to run at that width. We have addressed the original three issues. This is reusable, it explains what we are doing to the test, and the mobile width won't leak into other tests. We also don't need to add another layer of testing to our code like we did by adding the `with_mobile_width` helper, so the tests remain tidy and sustainable.

---

### Aha! is happy, healthy, and hiring. Join us!

This feature took a lot of tinkering and guidance from other engineers. Without such a knowledgeable and helpful team, I would not have arrived at this clean solution. If you want to build lovable software with this talented and growing team, [apply to an open role](https://www.aha.io/company/careers/current-openings?category=engineering).