Cucumber: Identifying slow steps that drag down your test speed
In most projects I know, Cucumber test suite speed is not an issue. Of course, running 350 features takes its time, but still each test for itself is reasonably fast. There is nothing you can do to fundamentally speed up such a test (of course, you should be using parallel_tests).
However, in projects that go beyond clicking around in a web GUI and checking results, there might be various measures to speed things up. Katapult tests for example could be sped up more than 4 times by re...
Rails + Sidekiq::Web: Configuration for wildcard session cookies
When you're using Sidekiq::Web to monitor the Sidekiq status AND have your session cookie configured to a wildcard domain like .example.com, you need to take an additional step to keep your cookies valid.
Issue
Sidekiq::Web is mounted into your Rails application and will use the Rails session cookie for protection from CSRF attacs. While it somehow figures out the cookie name, it does NOT respect cookie configuration like a custo...
How to install packages from newer Ubuntu releases
We're usually running Ubuntu LTS versions. Sometimes newer hardware requires packages from more recent Ubuntu releases that only come with 6 months of support. If there is really no other way, it's possible to install packages from later Ubuntu releases
Caution: Pay really close attention to what you're doing. Depending on the package, this process may require upgrading a lot of dependencies, possibly breaking the system! You really should not do this unless you've carefully calculated the impact on your system
Preparation
First,...
Capybara: Accessing the parent of an element
If you already selected an element and want to get its parent, you can call find(:xpath, '..') on it.
To get the grand-parent element, call find(:xpath, '../..').
Example
Find a link which contains a twitter icon and check that it links to the correct page:
<a href="http://twitter.com/">
<i class="icon is-twitter"></i>
</a>
link = page.find("a .icon.is-twitter").find(:xpath, '..')
expect(link[:href]).to eq("http://twitter.com/")
About XPath
There is a good overview on XPath syntax on [w3schools](htt...
Spreewald: Content-Disposition not set when testing a download's filename
Precondition
- You are not using javascript tests
- The file is served from a public folder (not via controller)
Problem description
If you deliver files from a public folder it might be that the Content-Disposition header is not set. That's why the following spreewald step might raise an error:
Then I should get a download with filename "..."
expected: /filename="some.pdf"$/
got: nil (using =~) (RSpec::Expectations::ExpectationNotMetError)
Solution
One solution...
Fixing flaky E2E tests
An end-to-end test (E2E test) is a script that remote-controls a web browser with tools like Selenium WebDriver. This card shows basic techniques for fixing a flaky E2E test suite that sometimes passes and sometimes fails.
Although many examples in this card use Ruby, Cucumber and Selenium, the techniques are applicable to all languages and testing tools.
Why tests are flaky
Your tests probably look like this:
When I click on A
And I click on B
And I click on C
Then I should see effects of C
A test like this works fine...
Async control flow in JavaScript: Promises, Microtasks, async/await
Slides for Henning's talk on Sep 21st 2017.
Understanding sync vs. async control flow
Talking to synchronous (or "blocking") API
print('script start')
html = get('/foo')
print(html)
print('script end')
Script outputs 'script start', (long delay), '<html>...</html>', 'script end'.
Talking to asynchronous (or "evented") API
print('script start')
get('foo', done: function(html) {
print(html)
})
print('script end')
Script outputs 'script start', 'script end', (long ...
Rails: namespacing models with table_name_prefix instead of table_name
When you want to group rails models of a logical context, namespaces are your friend. However, if you have a lot of classes in the same namespace it might be tedious to specify the table name for each class seperately:
class Accounting::Invoice < ApplicationRecord
self.table_name = 'accounting_invoices'
...
end
class Accounting::Payment < ApplicationRecord
self.table_name = 'accounting_payments'
...
end
A replacement for the self.table_name-assignment is the table_name_prefix in the module definition:
modu...
How to enable the Thinkpad microphone mute key on Ubuntu 16.04
While the hardware mute button of my Lenovo x230 worked on Ubuntu 14.04 out of the box, it does not on Ubuntu 16.04. It is fairly simple to fix, though.
There is an extensive answer on Ask Ubuntu, but only part of it was required for me. Here is the gist of it.
-
Open a terminal
-
Run
acpi_listenand press the mute key. You should see something like this:button/f20 F20 00000080 00000000 KPress
Ctrl+Cto exit. -
Run
amixer scontrols. You will see multiple lines, one of which sh...
How to change the class in FactoryBot traits
FactoryBot allows a :class option to its factory definitions, to set the class to construct. However, this option is not supported for traits.
Most often, you can just define a nested factory instead of a trait, and use the :class option there.
factory :message do
factory :reply, class: Message::Reply do
# ...
end
end
If you need/want to use traits instead (for example, it might make more sense semantically), you can not use a :class on a trait.
In that case, use initialize_with to define the record's constr...
Dealing with I18n::InvalidPluralizationData errors
When localizing model attributes via I18n you may run into errors like this:
I18n::InvalidPluralizationData: translation data { ... } can not be used with :count => 1. key 'one' is missing.
They seem to appear out of the blue and the error message is more confusing than helpful.
TL;DR A model (e.g. Post) is lacking an attribute (e.g. thread) translation.
Fix it by adding a translation for that model's attribute (attributes.post.thread). The error message reveals the (wrongly) located I18n data (from `attributes.thread...
Rspec: around(:all) and around(:each) hook execution order
Background
-
before(:all)runs the block once before all of the examples. -
before(:each)runs the block once before each of your specs.
Summary
-
around(:suite)does not exist. -
around(:all)runs afterbefore(:all)and beforeafter(:all). -
around(:each)runs beforebefore(:each)and afterafter(:each).
As this is not 100% obvious (and not yet documented) it is written down in this card. In RSpec 3 :each has the alias :example and :all the alias :context.
Example
RSpec.configure do |config|
...
Cucumber: Clear localStorage after each scenario
Capybara clears cookies before each scenario, but not other client-side data stores. If your app is using localStorage or sessionStorage, contents will bleed into the next scenario.
Use this hook to remove all site data after each scenario:
After do
if Capybara.current_driver == :selenium && !Capybara.current_url.starts_with?('data:')
page.execute_script <<-JAVASCRIPT
localStorage.clear();
sessionStorage.clear();
JAVASCRIPT
end
end
Rails: wrap_parameters for your API
Rails 5 (don't know about the others) comes with an initializer wrap_parameters.rb. Here you can tell rails to wrap parameters send to your controllers for specific formats into a root node which it guesses from the controller name.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
end
This would wrap a flat json body, like
{"name": "Konata"}
that gets send to your UsersController into
{"name" => "Konata", "user" => {"name" => "Konata"}}
Note that the params are now duplicat...
Selenium cannot obtain stable Firefox connection
When using geordi for integration tests you might get the following error when trying to run geordi cucumber:
unable to obtain stable firefox connection in 60 seconds (127.0.0.1:7055) (Selenium::WebDriver::Error::WebDriverError)
This means, that the vnc window the tests is talking to has no proper firefox version running. To figure out the issue this might help you:
- Check if the
.firefox-version(e.g.24.0) is the same as~/bin/firefoxes/24.0/firefoxsays in the browser - Maybe [rest...
How to make Webpacker compile once for parallel tests, and only if necessary
Webpack is the future. We're using it in our latest Rails applications.
For tests, we want to compile assets like for production.
For parallel tests, we want to avoid 8 workers compiling the same files at the same time.
When assets did not change, we do not want to spend time compiling them.
Here is our solution for all that.
Its concept should work for all test suites.
Copy the following to config/initializers/webpacker_compile_once.rb. It will patch Webpacker, but only for the test environment:
# Avoid hardcoded asset host...
Cucumber: Test that an element is not overshadowed by another element
I needed to make sure that an element is visible and not overshadowed by an element that has a higher z-index (like a modal overlay).
Here is the step I wanted:
Then the very important notice should not be overshadowed by another element
This is the step definition:
Then(/^(.*?) should not be overshadowed by another element$/) do |locator|
selector = selector_for(locator)
expect(page).to have_css(selector)
js = <<-JS
var selector = #{selector.to_json};
var elementFromSelector = document.querySelector(selector)...
Clicking issues with chromedriver
ChromeDriver clicking works by simulating a mouse click in the middle of the element's first client rect (or bounding client rect if it doesn't have a first client rect). (Clicking issues)
So if you are trying to click on an element, chromedriver tries to click at the position where it first finds that element.
This can lead to some problems and unexpected behavior like:
-
Element is not clickable at point ...erros - Another element which is now located at...
Git: Rebasing dependent feature branches
This card will show you how to use git rebase --onto without confusion.
Use case:
You've got two feature branches (one and two), where two depends on one. Now commits of branch one have changed after you branched two from it (i.e. after code review the commits of branch one are squashed). Thus the commit history of branch one has changed. Branch two's history however didn't change.
Solution:
To make the branches share the same commit history again you will have to rebase and replay (attach) the a...
Middleman configuration for Rails Developers
Middleman is a static page generator that brings many of the goodies that Rails developers are used to.
Out of the box, Middleman brings Haml, Sass, helpers etc. However, it can be configured to do even better. This card is a list of improvement hints for a Rails developer.
Gemfile
Remove tzinfo-data and wdm unless you're on Windows. Add these gems:
gem 'middleman-livereload'
gem 'middleman-sprockets' # Asset pipeline!
gem 'bootstrap-sass' # If you want to use Bootstrap
gem 'byebug'
gem 'capistrano'
gem 'capistrano-mid...
Style Guide for Git commit messages
- Separate subject from body with a blank line
- Limit the subject line to 50 characters (max. 72), include reference (unique story ID) to requirements tracker (Linear in our case)
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how.
Tip
As an alternative, use a commit message that refers to a GitHub issue (
fixes #321) or [story ID](https://makandracards.com/makandra/620718-b...
RubyMine users: you should be using bookmarks
RubyMine allows bookmarking lines of code. This is super-helpful when working on a complex problem.
I've been using this feature for a few years now, and so should you! :)
Here are the default Linux/Windows keystrokes. See the documentation for other keybindings.
Add an anonymous bookmark
F11
A gray checkmark will be shown in the gutter on the left.
If you press F11 again on a bookmarked line, the bookmark will be removed.
Add a named bookmark ("mnemonic")
Ctrl ...
How to disable Chrome's save password bubble for Selenium tests
When filling out forms in Selenium tests, Chrome shows the (usual) bubble, asking to store those credentials.
While the bubble does not interfere with tests, it is annoying when debugging tests. Here are two ways to disable it:
Option 1: prefs
You can set profile preferences to disable the password manager like so:
prefs = {
'credentials_enable_service' => false,
'profile.password_manager_enabled' => false
}
Capybara::Selenium::Driver.new(app, browser: :chrome, prefs: prefs)
Sadly, there are no command line s...
ruby-sass: Do not use comments between selector definitions
Sass lets you easily specify multiple selectors at once like this:
.some-block
&.has-hover,
&:hover
outline: 1px solid red
This will add a red outline on either real hover or when the has-hover class is present. However, adding a comment will void the definition of that line:
.some-block
&.has-hover, // From hoverable.js <-- DON'T
&:hover
outline: 1px solid red
... will simply drop the &.has-hover part in ruby-sass(deprecated). [sassc](https://rubygems.org/g...