| applyTo | spec/system/** |
|---|
System specs are located in spec/system/. Follow Best Practices and Development Patterns below when implementing system specs. We write system specs to simulate real user/admin interactions through the UI as much as possible. Interacting with underlying Javascript is discouraged; use Jasmine tests for Javascript-specific behavior.
The following information builds on For general Rspec standards: Rspec project coding standards
- ALWAYS use helper methods for system specs - read
spec/support/feature_support.rbbefore starting to implement system spec tests - Run
debug_process_statuswhen fields/sections can't be found - Wait for AJAX using
finish_page_loadingandfinish_form_formattingafter interactions - Expand UI sections before accessing fields - forms load via AJAX
- Never write raw Capybara selectors in system specs
- Use the VSCode internal browser (vscode/openSimpleBrowser) if needed
- Run the full system spec suite locally before pushing changes -
app-scripts/parallel_test.sh spec/system
| Task | Helper Method | Example |
|---|---|---|
| Text input | fill_in_field(name, value) |
fill_in_field('description', 'My text') |
| Dropdown | select_from_dropdown_field(name, value) |
select_from_dropdown_field('status', 'Active') |
| Multi-select | select_multiple_from_chosen(name, values) |
select_multiple_from_chosen('tags', ['A', 'B']) |
| Radio buttons | set_yes_no_field(name, value) |
set_yes_no_field('approved', 'yes') |
| Checkbox | set_checkbox_field(name, checked) |
set_checkbox_field('active', true) |
| Big select | select_from_big_select_field(name, value) |
select_from_big_select_field('grant', 'Title') |
| Expand section | expand_model_reference(name) |
form = expand_model_reference('Grant Aims') |
| Expand master by id | expand_master_record(master_id: id) |
expand_master_record(master_id: 123) |
| Expand master by text | expand_master_record(text: title) |
expand_master_record(text: 'Proposal') |
| Edit button | click_edit_button_within_target(elem) |
form = click_edit_button_within_target(form) |
| Wait for AJAX | finish_page_loading |
finish_page_loading |
| Debug current state | debug_process_status |
debug_process_status |
| Available fields | available_form_fields |
available_form_fields |
- Run
debug_process_statusto see current UI state - Check if section needs expanding:
expand_model_reference('Section Name') - Check if edit button needs clicking:
click_edit_button_within_target(form) - Verify show_if conditions are met for hidden fields
- As last resort:
debug_state('action_name', 'description')to save HTML snapshot, screenshot and UI status
To avoid 2FA logins blocking user tests, the following settings are recommended in test setup (in before(:all) block):
change_setting('TwoFactorAuthDisabledForUser', true)Never click on elements programmatically using Javascript if they may not be interactable. Instead, use JavaScript to scroll them into view first:
edit_link = find('a', text: 'edit tracker record')
scroll_into_view(edit_link) # From the FeatureHelper module
sleep 0.5 # Allow time for scrollingThen click the link:
click_link 'edit tracker record'If an HTML snapshot is needed for debugging, use the helper method:
save_html_snapshot('/tmp/debug_page.html')To capture console logs and CSP violations from the browser, use the helper methods:
# Set up capture AFTER initial page load, BEFORE navigating to page to debug
visit '/'
setup_browser_console_capture
# Navigate and perform actions you want to debug
visit '/page/to/debug'
finish_page_loading
click_button 'Submit'
# Print captured logs with context description
print_browser_console_logs('After clicking Submit')
# Or retrieve logs programmatically without printing
result = get_browser_console_logs
puts "CSP violations: #{result[:csp_violations].count}"
result[:logs].each { |log| puts log }The capture methods intercept console.log, console.error, console.warn, and CSP violation events.
- Standard string / varchar fields downcase data on storage and titleize on display. Keep this in mind when writing system specs that interact with user data fields.
- Some fields rely heavily on Javascript for rendering and interaction (e.g., chosen.js dropdowns, big select dialogs, custom rich text editors). Always use the provided helper methods to interact with these fields.
- Field visibility is controlled by
show_ifrules. Always set prerequisite fields first and allow time for the UI to update. - Don't navigate directly to edit URLs; always use the UI flow to reach forms (e.g. </masters/123> then click edit button for the appropriate block).
- Route Changes happen when adding dynamic definition configurations -
DynamicModel.routes_reloadshould be automatically run after model config changes, but may need manual invocation for some tests to work
- Did you expand the section? → Use
expand_model_reference('Section')orexpand_embedded_reference('Name') - Did you click edit button? → Use
click_edit_button_within_target(form) - Are show_if conditions met? → Set prerequisite fields first, add
sleep 1 - Check field name → Run
available_form_fieldsto see actual field names - Still not found? → Run
debug_process_statusand examine output
- Scroll element into view → Use
scroll_into_view(element)or helper handles it automatically - Wait for AJAX → Call
finish_page_loading - Check if hidden by CSS → Use helper methods which handle
visible: false - Check if in collapsed section → Expand section first
- Check if read-only view → Click edit button to make form editable
- Check database relationships → Review YAML configuration foreign keys
- Verify test data exists → Check setup creates necessary records
- Debug available options →
puts field.all('option').map(&:text).inspect - Check query configuration → Examine
blank_preset_valuein YAML
- Timing issues → Add
finish_page_loadingorfinish_form_formattingafter AJAX interactions - Race conditions → Add small
sleepafter triggering show_if rules - Different data state → Ensure setup is idempotent and complete
- Browser differences → Test with both Chrome and Firefox if possible
Attempt to follow the real user / admin flow through the UI as much as possible, avoiding direct model manipulation except for setup/teardown. Avoid using visit to go directly to pages that would not normally be accessible through the UI flow.
For example:
# Don't do this:
visit "/redcap/project_admins/edit/#{project.id}"
# Do this instead:
visit "/redcap/project_admins?filter[id]=#{project.id}&perform_action=edit"
finish_page_loading
# If this doesn't work for some reason, check for Javascript errorsAny "edit" button represented by a glyphicon should be clicked in the UI rather than visiting the edit URL directly. These buttons typically have the HTML class something like: "edit-entity glyphicon glyphicon-pencil".
Any link or button that has the HTML attribute data-remote="true" (which may appear in a Rails helper like <%= link_to ..., remote: true %>) should be clicked in the UI rather than visiting the URL directly. This is because these links typically perform AJAX requests that update parts of the page dynamically.
Don't use Javascript to manipulate or show fields not visible due to show_if rules. These are hidden due to the business logic, and if the tests dictate they should be shown then this indicates a bug.
Avoid relying on skip or xit in spec files. Instead, fix the underlying issues causing test failures. The aim is not to have tests that simply run without errors, but to have tests that accurately verify the functionality.
If changes are made to a spec file, make sure to re-run the tests to verify they still pass. Nothing should be considered "fixed" until the tests pass successfully.
These patterns are essential for successful test implementation.
CRITICAL: ReStructure provides comprehensive helper methods in spec/support/feature_support.rb. Always use these helpers rather than writing raw Capybara selectors or inspecting HTML directly. The helpers handle AJAX waits, scrolling, visibility issues, and provide debug output when things fail.
Cause: Field doesn't exist in current DOM state
Solutions:
- Expand section:
expand_model_reference('Section Name') - Click edit:
click_edit_button_within_target(target) - Check show_if: Set prerequisite fields first
- Debug:
debug_process_statusoravailable_form_fields
Cause: Element exists but can't be clicked/filled
Solutions:
- Scroll into view:
scroll_into_view(element) - Use helper method (handles visibility):
fill_in_field('name', 'value') - Wait for AJAX:
finish_page_loading - Check if hidden by show_if rules
- Check if read-only view (click edit button first)
Cause: Element reference outdated after DOM update
Solutions:
- Re-find element after AJAX:
element = find('.class') - Use
finish_page_loadingbefore re-finding - Use within block to limit scope:
within target { ... } - Store container, not individual elements
Cause: Form validation failed silently
Solutions:
- Check validation:
puts_form_validation_errors - Check alerts:
puts_alerts - Take snapshot:
debug_state('after_submit', 'checking validation') - Verify all required fields filled
Cause: Selector matches multiple elements
Solutions:
- Use more specific selector with ID or unique class
- Use
withinblock to limit scope:within '#specific-form' { ... } - Use helper methods which handle specificity
- Check if duplicate elements exist in DOM
Field Interaction Helpers:
# DO NOT write raw selectors - use helpers!
fill_in_field('description', 'My description') # Text fields
select_from_dropdown_field('funding_agency', 'NIH') # Dropdowns (handles chosen.js)
select_multiple_from_chosen('topics', ['Cancer']) # Multi-select
set_yes_no_field('reviewed_yes_no', 'yes') # Radio buttons
set_checkbox_field('is_selectable', true) # Checkboxes
edit_rich_text_editor_field('notes', 'Rich text') # Custom editors
select_from_big_select_field('select_grant', 'Title') # Big select dialogsSection Expansion Helpers:
expand_master_record(text: 'Proposal Title') # Master record panel
form = expand_model_reference('Grant Aims') # mr-expander sections
form = expand_embedded_reference('Grant Funded') # Embedded references
form = click_edit_button_within_target(target_element) # Edit buttonsDebug Helpers (USE DURING DEVELOPMENT):
debug_process_status # Shows EVERYTHING: alerts, fields, sections, buttons
user_instructions_placeholders # User guidance text
available_form_fields # All fields with types, visibility, values
available_model_reference_expanders # Expandable sections
available_submit_fields # Submit buttons
available_report_tabs # Report tabs
puts_form_validation_errors # Which fields failed validation
puts_alerts # Flash messages
puts_modals # Open modalsNavigation Helpers:
finish_page_loading # Wait for AJAX
finish_form_formatting # Wait for UI formatting
click_report_tab('My Grant Aims') # Click report tabs
scroll_into_view(element) # Scroll element
save_html_snapshot('/tmp/debug.html') # Save HTML (last resort)Enable debug output:
app-scripts/headless_rspec.sh spec/system/your_spec.rb -e 'the example to run'Many forms in ReStructure display in read-only view initially and require clicking an edit button to load the editable form via AJAX.
Pattern Recognition:
- Forms showing as list items (
<li>) with field values displayed as text - Edit button (pencil icon) with classes like
edit-entity glyphicon glyphicon-pencil - Edit button has
data-remote="true"attribute - Specific edit button classes follow pattern:
edit-{resource_name}(e.g.,edit-activity-log--project-assignment-grant-aim-grant-funded)
Implementation using helpers:
# WRONG: Try to fill fields immediately
within '#mr-expander-grant-funded' do
fill_in 'reviewed_yes_no', with: 'yes' # ❌ Field doesn't exist yet!
end
# CORRECT: Use helper to expand and click edit button
form = expand_embedded_reference('Grant Funded details')
form = click_edit_button_within_target(form)
finish_page_loading # Wait for AJAX
# NOW use helpers to fill fields
within form do
set_yes_no_field('reviewed_yes_no', 'yes')
set_checkbox_field('is_selectable', true)
end
# CORRECT: Use helper to expand and click edit button
form = expand_embedded_reference('Grant Funded details')
form = click_edit_button_within_target(form)
finish_page_loading # Wait for AJAX
# NOW use helpers to fill fields
within form do
set_yes_no_field('reviewed_yes_no', 'yes')
set_checkbox_field('is_selectable', true)
endKey Points:
- Use
expand_embedded_referenceto expand sections - Use
click_edit_button_within_targetto click edit buttons - Use field helpers (
set_yes_no_field,set_checkbox_field) for form fields - Helpers handle scrolling, visibility, and AJAX waits automatically
Hidden Form Fields (Custom UI Components)
Many form fields use custom styling with visible: false CSS, particularly radio buttons and checkboxes. Use the helper methods - they handle visibility automatically.
Implementation using helpers:
# CORRECT: Helpers handle visibility automatically
set_yes_no_field('reviewed_yes_no', 'yes') # ✅ Handles hidden radio
set_checkbox_field('is_selectable', true) # ✅ Handles hidden checkboxDiscovery Process (when fields not found):
- Use helper first: The helper will show available fields in error message
- Check process state:
debug_process_statusoravailable_form_fields - Only inspect HTML as last resort:
save_html_snapshot('/tmp/debug.html')
Activity log forms often use collapsible sections (mr-expander) that load content via AJAX when expanded. Form fields do not exist until the section is expanded. Use the helper method to handle this automatically.
Pattern Recognition:
- Caret icons (▶) with classes
glyphicon-triangle-right - Elements with IDs like
#mr-expander-{section-name}(e.g.,#mr-expander-grant-aims) - Attribute
data-remote="true"on section links - Attribute
data-toggle-caret="true"
Implementation using helpers:
# WRONG: Try to access fields without expanding section
click_link proposal_title
fill_in 'Description', with: text # ❌ Field doesn't exist yet!
# CORRECT: Use helper to expand section
expand_master_record(text: proposal_title)
form = expand_model_reference('Grant Aims')
finish_page_loading # Helper handles this, but good practice
# NOW use helpers to fill fields
within form do
fill_in_field('description', text)
select_from_dropdown_field('funding_agency', 'NIH')
endScroll Elements into View:
scroll_into_view(element)Debug Technique (when developing system specs):
# See what sections are available
debug_process_status # Shows all model reference expanders
# Or specifically:
available_model_reference_expanders # Lists all expandable sectionsFields may appear or disappear based on other field values via show_if configuration rules. This is pure business logic - DO NOT circumvent it by injecting JavaScript or forcibly showing hidden fields.
Common Pattern:
# Field A controls visibility of Field B
select 'Grant', from: 'funding_source' # Must select this first
sleep 1 # Allow show_if rules to process
# NOW Field B becomes visible
select_from_big_select_field('select_grant', grant_title)Key Points:
- Always set prerequisite fields in the correct order
- Add small sleeps (0.5-1s) after changing fields that trigger show_if rules
- If a field can't be found, check if its show_if conditions are met
- Don't use JavaScript to force-show hidden fields - this indicates a test logic bug
- Check field configuration in YAML to understand dependencies
Debugging show_if Issues:
# Check if field exists but is hidden
all('input[name*="field_name"]', visible: :all).count # Should be > 0 if field exists
# If count is 0, field doesn't exist yet (wrong section or conditions not met)
# If count > 0 but field not interactable, check show_if conditionsSearch forms use specific field naming patterns that may differ from display labels.
Discovered Patterns:
search_attrs[title]- for searching by title (NOTsearch_attrs[text])search_attrs[description]- for description searches- Field names in search forms are often prefixed with
search_attrs[...]
Implementation:
# WRONG: Guess the field name
fill_in 'Search', with: title # ❌ Ambiguous
# CORRECT: Use exact field name
fill_in 'search_attrs[title]', with: title, wait: 5Discovery Process:
# Extract search field names from HTML
irb --noverbose <<EOF
require 'nokogiri';
file = File.open("/tmp/search_page.html", "rb");
page = Nokogiri::HTML(file.read);
result = page.css('input[name*="search_attrs"]');
result.map {|r| r['name']}.join("\n")
EOFAfter form submissions, validation happens asynchronously. Always wait for visible confirmation text, not hidden text.
Pattern:
# WRONG: Check for text that might be in non-visible areas
click_button 'Submit Proposal'
expect(page).to have_content('Project Submitted on', wait: 10) # ❌ May be in collapsed section
# CORRECT: Check for text that's always visible after the action
click_button 'Submit Proposal'
finish_page_loading
expect(page).to have_content('Proposal is awaiting review', wait: 10) # ✅ Visible status messageValidation Error Handling:
expect_no_validation_errorsForm option values may have unexpected capitalization that differs from configuration.
Example:
# Configuration shows:
funding_source:
- grant
- no_funding# But actual HTML has:
# <option value="Grant">Grant</option> # Capital G!
# WRONG:
select 'grant', from: 'funding_source' # ❌ Option not found
# CORRECT:
select 'Grant', from: 'funding_source' # ✅ Matches HTML exactlyDiscovery:
# Check available options
funding_field = find('select[name*="funding_source"]')
options = funding_field.all('option').map(&:text)
puts "Available options: #{options.inspect}"
# Output: ["", "No funding", "Grant", "(other)"]Big select fields query database tables with specific foreign key relationships. Understanding these relationships is critical.
Example Issue:
# Big select configuration for Analysis Plans:
select_grant:
field_type: select_record_id_from_table_view_active_grants
blank_preset_value:
dynamic_model__viva_grants:
activity_log_project_assignment_grant_aim_id: "{{activity_log__project_assignment_grant_aims.id}}"Problem: Analysis Plans are activity_log__project_assignments (different table), so the query for grants with matching activity_log_project_assignment_grant_aim_id returns nothing.
Lesson: When big select returns no results:
- Save the HTML:
File.write('/tmp/big_select.html', page.html) - Check what items are available (may be just
["-1", "big-select-clear"]) - Examine the YAML configuration for the field's query
- Verify the foreign key relationships match the actual table structure
- Determine if configuration needs updating or if test approach needs adjustment
Organize feature spec helpers by responsibility for maintainability:
Structure:
spec/support/{feature_name}_feature_support/
├── {feature}_setup.rb # Database, config import, access controls
├── {feature}_user_setup.rb # User creation, role assignment
├── {feature}_login.rb # Authentication flows
├── {feature}_navigation.rb # Page navigation, waiting helpers
├── {feature}_actions.rb # UI interactions, form filling
├── {feature}_expectations.rb # Assertions and validations
└── z_{feature}_main.rb # Main module that includes all others
Pattern:
# z_grant_aims_main.rb
module GrantAimsFeatureSupport
ACTIVITY_LOG_NAME = 'Grant Aims'
APP_TYPE_NAME = 'Projects'
# Include all sub-modules
include GrantAimsSetup
include GrantAimsUserSetup
# ... etc
end
# In spec file:
RSpec.describe 'Grant Aims Process', type: :feature do
include GrantAimsFeatureSupport
it 'completes full workflow' do
# Use helpers from included modules
end
endTwo-factor authentication settings must be configured BEFORE other setup code.
Pattern:
# WRONG:
before(:all) do
SetupHelper.feature_setup
change_setting('TwoFactorAuthDisabledForUser', true) # ❌ Too late!
end
# CORRECT:
before(:all) do
change_setting('TwoFactorAuthDisabledForUser', true) # ✅ Before setup
change_setting('TwoFactorAuthDisabledForAdmin', true)
SetupHelper.feature_setup
end# 1. Expand master record
expand_master_record(text: proposal_title)
finish_page_loading
# 2. Expand activity log section (loads via AJAX)
form = expand_model_reference('Grant Aims')
finish_page_loading
# 3. Click edit button to make form editable (if showing read-only view)
form = click_edit_button_within_target(form)
finish_page_loading
# 4. Fill conditional fields in correct order
within form do
# First, set field that controls show_if visibility
select_from_dropdown_field('funding_source', 'Grant')
sleep 1 # Allow show_if rules to process
# Now dependent field becomes visible
select_from_big_select_field('select_grant', grant_title)
# Fill remaining fields
fill_in_field('description', 'My description')
set_yes_no_field('approved', 'yes')
set_checkbox_field('is_selectable', true)
end
# 5. Submit and verify
click_submit_field('Submit for Review')
finish_page_loading
expect_no_validation_errors
expect(page).to have_content('Submitted successfully', wait: 10)# 1. Navigate to search page
visit '/dynamic_model/grant_aims'
finish_page_loading
# 2. Fill search form with exact field names
fill_in 'search_attrs[title]', with: grant_title, wait: 5
click_button 'Search'
finish_page_loading
# 3. Verify result appears
expect(page).to have_content(grant_title, wait: 10)
# 4. Expand master record
expand_master_record(text: grant_title)
finish_page_loading
# 5. Expand and edit section
form = expand_embedded_reference('Grant Details')
form = click_edit_button_within_target(form)
finish_page_loading
# 6. Update fields
within form do
fill_in_field('description', 'Updated description')
select_from_dropdown_field('status', 'Active')
end
# 7. Save changes
click_submit_field('Save')
finish_page_loading
expect_no_validation_errors# When you can't find a field or section:
# Step 1: Check overall page state
debug_process_status
# This shows: alerts, tabs, modals, sections, fields, buttons
# Step 2: Check specific items
available_form_fields # See all fields with visibility
available_model_reference_expanders # See expandable sections
available_submit_fields # See submit buttons
puts_alerts # Check for error messages
# Step 3: If still stuck, save state
debug_state('stuck_point', 'cannot find description field')
# This saves HTML snapshot and screenshot
# Step 4: Analyze saved HTML
# In terminal:
# irb --noverbose <<EOF
# require 'nokogiri'
# file = File.open("/tmp/stuck_point.html", "rb")
# page = Nokogiri::HTML(file.read)
# result = page.css('[data-attr-name]')
# result.map {|r| r['data-attr-name']}.join("\n")
# EOFRSpec.describe 'My Feature', type: :system do
before(:all) do
# ✅ 1. Disable 2FA FIRST (before any other setup)
change_setting('TwoFactorAuthDisabledForUser', true)
change_setting('TwoFactorAuthDisabledForAdmin', true)
# ✅ 2. Import configs and setup database
SetupHelper.feature_setup
# ✅ 3. Create test user with proper access
@user = create_user('testuser@example.com')
setup_access(@user, 'Grant Aims', user_type: 'read_write')
# ✅ 4. Create necessary test data
@grant = create_test_grant
@proposal = create_test_proposal
end
before(:each) do
# ✅ 5. Login for each test
login_as(@user, scope: :user)
end
it 'completes the workflow' do
# ✅ 6. Navigate to feature
visit '/dashboard'
finish_page_loading
# Your test code here...
end
end- ✅ Run
debug_process_statusliberally - see what's actually on the page - ✅ Use
debug_state(name, desc)at key points - save state for analysis - ✅ Save HTML snapshots when stuck -
save_html_snapshot('/tmp/debug.html') - ✅ Check for JavaScript errors - look in browser console output
- ✅ Use helper methods exclusively - don't write raw Capybara selectors
- ✅ Add
finish_page_loadingafter AJAX - ensure DOM is ready - ✅ Follow real user flow - don't use
visitto skip steps
- ✅ Run spec to verify it passes -
bundle exec rspec spec/system/my_spec.rb - ✅ Remove debug statements - clean up
debug_process_status,debug_state, etc. - ✅ Remove snapshots and screenshots - delete temporary debug files
- ✅ Remove
skiporxit- fix issues instead of skipping tests - ✅ Verify test follows real user flow - no artificial shortcuts with
visit - ✅ Check test isolation - ensure test doesn't depend on other tests
- ✅ Add comments for complex show_if logic - explain conditional field dependencies
Print and save state (including HTML snapshot and screenshot) during test development to diagnose issues.
debug_state(name, description)To extract using CSS selectors from a stored HTML file:
Get the first heading with class some-classification tag
irb --noverbose <<EOF
require 'nokogiri';
file = File.open("/tmp/saved.html", "rb");
page = Nokogiri::HTML(file.read);
first_style_tag = page.css('h1.some-classification')[0];
first_style_tag.text
EOFGet all data-attr-name attribute values
irb --noverbose <<EOF
require 'nokogiri';
file = File.open("/tmp/grant_aims_expanded.html", "rb");
page = Nokogiri::HTML(file.read);
result = page.css('[data-attr-name]');
result.map {|r| r['data-attr-name']}.join("\n")
EOFUse screenshots for debugging complex UI issues. To save a screenshot during a system spec test, use the following pattern:
take_screenshot(name, description, force: true)If an AI Agent can't read the screenshot directly, extract text from the screenshot image using Tesseract OCR:
tesseract /path/to/screenshot.png stdoutShows a YAML representation of the following information based on the state of the UI:
- error page block, if an error page rather than intended page is shown
- visible alerts
- report tabs
- visible modals
- for a stacked activity log process:
- available mr-expander sections
- user instructions placeholders at the top of the primary activity log block
- available form fields with types, visibility, and current values
- available submit buttons
- visible embedded model reference "add" buttons
debug_process_status If you need to dig deeper into the HTML, use Capybara selectors:
# Find what sections are available
all('.mr-expander').each do |elem|
puts_debug "ID: #{elem[:id]}, Text: #{elem.text}"
end
# Find all forms
all('form').each do |form|
puts_debug "Action: #{form[:action]}, Method: #{form[:method]}"
end
# Find all inputs in a section
within result_target do
all('input, select, textarea', visible: :all).each do |field|
puts_debug "Name: #{field[:name]}, Type: #{field[:type]}"
end
end| Pitfall | Symptom | Solution |
|---|---|---|
| Field not found | Unable to find field "Name" |
Check if mr-expander section is expanded |
| Element not interactable | ElementNotInteractableError |
Add visible: :all or scroll into view |
| Stale element | StaleElementReferenceError |
Re-find element after page updates |
| Wrong option selected | Unable to find option "value" |
Check actual option values (capitalization) |
| Validation not working | Form submits but data not saved | Check if edit button was clicked to load editable form |
| AJAX timing issues | Intermittent failures | Use finish_page_loading and proper waits |
| Empty big select | Only shows clear option | Check table relationships in query configuration |
| Ambiguous match | Ambiguous match, found 2 elements |
Use more specific selector (ID, unique class, within block) |
| If you see in HTML... | Pattern type | Use this helper |
|---|---|---|
<li> with text values, pencil icon |
Edit button AJAX | click_edit_button_within_target() |
Caret icon ▶, data-remote="true" |
Collapsible section | expand_model_reference() |
<select class="use-chosen"> |
Chosen dropdown | select_from_dropdown_field() |
<select multiple> |
Multi-select chosen | select_multiple_from_chosen() |
<input class="use-big-select"> |
Big select dialog | select_from_big_select_field() |
Radio with visible: false CSS |
Hidden yes/no field | set_yes_no_field() |
| Form field appears/disappears | show_if conditional | Set prerequisite + sleep 1 |
data-toggle-caret="true" |
mr-expander section | expand_model_reference() |
Class edit-entity glyphicon-pencil |
Edit button | click_edit_button_within_target() |
data-remote="true" link/button |
AJAX action | Click in UI, don't visit URL directly |
NEVER write raw Capybara code like this:
find('input[name="description"]').set('value') # ❌ DON'T DO THIS
find('select#status').select('Active') # ❌ DON'T DO THIS
find('.glyphicon-pencil').click # ❌ DON'T DO THISALWAYS use helper methods:
fill_in_field('description', 'value') # ✅ DO THIS
select_from_dropdown_field('status', 'Active') # ✅ DO THIS
form = click_edit_button_within_target(form) # ✅ DO THISWhy helpers are critical:
- Handle AJAX waiting automatically
- Scroll elements into view
- Handle hidden field visibility (
visible: false) - Provide detailed error messages with context
- Handle chosen.js, big select, and custom UI components
- Consistent behavior across all tests