Switch off Capybara::ElementNotFound / avoid nested rescue - ruby-on-rails

I've got an issue with a method shared across a wide number of integration tests.
The problem is, I need to find one of two buttons, and have so far only come up with the following unwieldy syntax for avoiding Capybara's ElementNotFound error:
new_button = begin
find(".button_one")
rescue Capybara::ElementNotFound
begin
find('.button_two')
rescue Capybara::ElementNotFound
raise Capybara::ElementNotFound, "Missing Button"
end
end
new_button.click
This works as expected: if the first button's not found, the second one is, and they're clicked. If neither are present, the error is raised.
Despite this, I really don't like the nested rescues and would like to tidy this up.
The simplest solution which feels like it should exist, though I've not found this anywhere: does anyone know if there's an option to return nil in Capybara's find method, rather than raising the exception?
For example, the following pseudocode...
new_button = find('.button_one', allow_nil: true) || find('.button_two', allow_nil: true)
new_button ? new_button.click : raise(Capybara::ElementNotFound, "Missing Button")
...would be perfect.
Otherwise, any suggestion of how best to rescue the two errors and avoid the horrible nested rescue?
Footnote: this code exists within a large existing structure, which previously worked fine where it shouldn't have. Fixing another issue has caused this problem, which is used widely throughout the suite. I'd love to adjust the calls and use the correct elements (and so avoid this altogether), though that's going to be a big project a little later in the day.

If only one of the buttons is ever on the page, the simplest solution is to just look for both buttons at once using the CSS comma
find(".button_one, .button_two").click
If it's possible for both buttons to be on the page at the same time then that will get you an Ambiguous match error, in which case you could do something like
find(".button_one, .button_two", match: :first).click
or
all(".button_one, .button_two")[0].click
It's also possible to check whether an element exists without raising an exception using the Capybara provided predicates has_css?/has_xpath?/etc. which would give code like
if page.has_css?(".button_one")
find(".button_one")
else
find(".button_two")
end.click
but in this case using the CSS comma would definitely be the better solution.

Try with the x-path //button[contains(#class,'button_one') or contains(#class, 'button_two'] as given below,
new_button = begin
find(:xpath, "//button[contains(#class,'button_one') or contains(#class, 'button_two']")
rescue Capybara::ElementNotFound
raise Capybara::ElementNotFound, "Missing Button"
end
new_button.click

I'm not familiar with Ruby, so I'll leave the link to Ruby docu and Capybara docu. The idea is to use find_elements instead of find_element. Why? find_elements won't throw any exception if there is no elements found. The pseudo code would be like this:
new_button = find_elements('.button_one').size() > 0 ? find('.button_one') || find('.button_two')
And you don't need to handle with exceptions anymore.

Related

How do I fix: ArgumentError: invalid byte sequence in UTF-8?

I am getting this type of error in the logs :
Parameters: {"id"=>"4", "step"=>{"documents_attributes"=>{"0"=>
{"file"=>"\x89PNG\r\n\u001A\n\u0000\u0000\u0000\rIHDR\u0000\..."}}}}
def update
#step = Step.find_by(id: params[:id])
if #step.update(steps_params)
render :json => #step
else
render :json => { :responseStatus => 402,
:responseMessage => #step.errors.full_messages.first}
end
end
During update, it rollbacks without giving any error (not execute else condition)
ArgumentError (invalid byte sequence in UTF-8):
(0.2ms) ROLLBACK
How can I fix or handle this type of request?
Your question is how to handle this type of request or error. So here is my suggestion of a general strategy.
First, do your homework. You could easily find this past question, for example. If you have tried the way already but found it did not work, you should have described what you did and what did not work in your question.
Now, I am assuming you can reproduce the case or at least you can expect you will encounter the same problem in near future (or you can wait till then) so you will have a more chance to pin down the problem next time. If you know what parameters caused the error, I guess you can reproduce the case in your development environment. However, if not, it is more tricky to pin down — it heavily depends how much information about the error and input you have and what development environment you can use, and my answer does not cover the case.
The first objective should be to pin down which command (method) exactly in your code caused an error. Did it happen just inside Rails or did your DB raise an error?
In your specific case, did it occur at Step.find_by or #step.update or else? What is steps_params? It seems like a method you have defined. Are you sure steps_params is working as expected? (You may be sure, but we don't know…)
A convenient way to find it out is simply to insert logger.debug (or logger.error) etc before and after each sentence. In doing it, it is recommended to split a sentence into smaller units in some cases. For example, steps_params and update() should be separated, such as (in the simplest case),
logger.debug 'Before steps_params'
res_steps_params = steps_params
logger.debug 'Before update'
res_update = #step.update(res_steps_params)
logger.debug 'Before if'
if res_update
# ……
Obviously you can (and perhaps should) log more detailed information, such as, res_steps_params.inspect, and you may also enclose a part with a begin-rescue clause so that you can get the detailed infromation about the exception and log it. Also, I can recommend to split update into 2 parts – substitutions and save – to find out exactly what action and parameter cause a problem.
Once you have worked out which of DB or Rails or something before (like HTTP-server or Client-browser) is to blame and which parameter causes a problem, then you can proceed to the next stage. The error message suggests it is a character-encoding issue. Is the character encoding of a string invalid (as a UTF-8), or wrongly recognised by Rails (which might be not a fault of Rails but of the client), or not recognised correctly by the DB?
Wherever the problem lies, it is usually (though not always!) possible to fix or circumvent character-encoding problems with Ruby (Rails). The Ruby methods of String#encode, String#encoding, and String#force_encoding would be useful to diagnose and perhaps fix the problem.
As an added note, it can be useful, if possible in your environment, to browse the logfile of your DB (PostgreSQL?) to find out which query passed from Rails to the DB caused a problem (if a query was indeed passed to them!). Alternatively, Rails Gem SQL Query Tracker might be handy to know what queries your Rails app create (though I have never used it and so can't tell much.)
At the end of the day, when a code misbehaves mysteriously, I am afraid only the sure way to solve is to narrow down the problematic clause or parameter step by step. Good luck!

Testing rails for race conditions, and cleaning up afterwards

I'm trying to test parts of my code for race conditions. The issue I had was related to uniqueness validations, which as it turns out is not safe from race conditions in rails. I believe I'll be able to fix the issue, but I'm not sure how to test my solution.
The closest I've come is the following(inspired by: http://blog.arkency.com/2015/09/testing-race-conditions/):
test "Can't create duplicate keys with same value and keyboard" do
assert_equal(5, ActiveRecord::Base.connection.pool.size)
begin
concurrency_level = 4
keyboard = create :keyboard
should_wait = true
statuses = {}
threads = Array.new(concurrency_level) do |i|
Thread.new do
true while should_wait
begin
# Unique validation for key values exists scoped to keyboard
key = keyboard.keys.new(value: 'a')
statuses[i] = key.save
rescue ActiveRecord::RecordNotUnique
statuses[i] = false
end
end
end
should_wait = false
threads.each(&:join)
assert_equal(1, keyboard.keys.count)
assert_equal(1, statuses.count { |_k, v| v })
assert_equal(3, statuses.count { |_k, v| !v })
ensure
ActiveRecord::Base.connection_pool.disconnect!
end
end
The code above is structured exactly like mine, but models have changed to be more general.
The test itself seems to work alright. However, keys created in the tests are not deleted afterwards. I'm using DatabaseCleaner, and I've tried all different strategies. Also, sometimes I get a Circular Dependency issue for constant Key. Not sure why, but I'm guessing its due to requires not being thread safe in ruby?
Is there a better way to my problem? As I specified above, I've gotten a few different issues with this, and I feel it should be a common enough problem that good testing standards should exist.
A few things:
1) Probably my ignorance, but the true while should_wait line seems wrong to me. Something more like while should_wait do seems more like what you intend. You also call pod.save which seems to not make sense, so I'm guessing this is not exactly the code you're using.
2) I would expect database cleaner to work, because I think if you use the "truncation" strategy it will go through and truncate every table when the test is run. My wild ass guess is that you have configured it to only run for integration tests and this is a unit test, or something like that. If that's not it, try calling DatabaseCleaner.truncate (or however you do that explicitly) at the end of the test and see if that works.
3) Can you solve the problem with a unique index in your DB? That removes the need for this test at all because you get to just trust your database. When you do get a non-unique value you can handle that in a non-validation way in your code. Much faster too, because you don't have to make the extra sql call every time you save.
4) Impossible to know from the information given why you're getting the circular dependency issue. I've had that issue before and did a puts caller at the top of the file to try to diagnose.

Friendly way to show MalformedCSVError to non-english speakers

I want provide as much feedback as possible to users trying to import data from csv. The problem is on the cases I need to rescue CSV::MalformedCSVError because the exception message is in english(I can't use english) and there is no other way to distinguish one error from another.
What can I do to show the exact problem to the user?
I noticed you tagged this as Rails so that means you have I18n. Why don't you do something like the following:
begin
# csv parsing code
rescue CSV::MalformedCSVError => ex
raise(CSV::MalformedCSVError.new(I18n.t("csv_parser.malformed_csv_error"))
end
Note: this does assume CSV::MalformedCSVError inherits from StandardError which might not be the case but you get the idea, raise an exception and set the message to some translated I18n string.
UPDATE:
If you wanted even more detail you could match against the exception message and have translations for each message type, while capturing the data you want from the error message string - for instance line number etc. I have no idea right now what the error messages look like but say you have something like "error in column 45" then you could do the following
begin
# csv import code
rescue CSV::MalformedCSVError => ex
err_message = case ex.message
when /column (\d+)/
I18n.t("csv_error.column_error_message", column: Regexp.last_match[1])
else
I18n.t("csv_error.generic_message")
end
raise(CSV::MalformedCSVError.new(err_message))
end
UPDATE: The GEM
I've taken this monkey patch and turned it into a gem. Check it out, along with a test suite:
https://github.com/jhubert/csv-i18n
Original Answer
I bumped into this problem and decided that unless I was ok with showing unhelpful error messages to my users, I could go with two options:
Introduce a new class like FriendlyCSV that wrapped the CSV parser, examined the exception message and returned a relevant error message in the right language.
Monkey-patch the CSV library to return translated error messages.
The first way is probably the proper way to go, since I control the source code and can use whatever class I want to process the incoming CSV files.
However, I'm far too reckless for that and I liked the idea that ANY CSV parsing done anywhere in the system returned translated messages, without other people needing to be aware of the FriendlyCSV class.
So, I went with #2.
The Monkey Patch
You can find the patch here:
https://gist.github.com/jhubert/7d75586857d41fb4c45c4491363636e9
The core behavior is that we're overwriting the shift method, which is where the bulk of the string parsing is. We're attempting to translate the error message and then returning the original exception.
def shift
super
rescue ::CSV::MalformedCSVError => exception
raise $!, translated_exception_message(exception.message), $!.backtrace
end
Hope that helps!

Rails 3 - How to deal with complicated Switch Statements / If Statements

I'm building a method that ingests incoming email, and processes the email. Along the way there are a lot of things that could prevent the email from being processes successfully. The wrong reply-to address, the wrong from address, an empty message body etc..
The code is full of Switch Statements (case/when/end) and If statements. I'd like to learn a smarter, cleaner way of doing this. Additionally, a way to can track an error and at the end have one location where it emails back the user with an error. Is something like this possible with rails?
#error = []
Case XXX
when xxxx
if XXXXX
else
#error = 'You don't have permission to reply to the xxxxx'
end
else
#error = 'Unfamilar XXXX'
end
Then something at the end like...
If #errors.count > 0
Send the user an email letting them know what went wrong
else
do nothing
end
Thanks for the help here. If you know of any other tutorials that would teach me how to write logic like the above smarter, that'd be great. Right now I have case/if statements going 3 levels deeps, it's hard to keep it straight.
Thanks
First, I would just assign a symbol to each error message as a simple hash:
ErrorsDescription = {
:first => "First error",
:second => "Second error",
...
}
And use symbols instead of strings.
Then, your if and switch statements. Basicaly I can't really help you, because I don't see what kind of condition statements you have. What are you checking? Why do you have 3 level deep conditions? Probably you can write it simpler using if and switch - so this is my first answer to this issue. Another solution may be writing simple methods to improve readability, so you can write like this:
if #email.has_wrong_reply_to_address?
#errors << :wrong_reply_to_address
else
...
end
Also, as #mpapis suggested, you can use Rails build in validation system, but not as ActiveRecord but as ActiveModel. Here you have some examples how to do it and how it works (also take a look here). Of course you may need to write custom validations, but they are just simple methods. Once you do all above job, you can just use:
#email.valid?
And if it is not, you have all errors in hash:
#email.errors
Just as in ordinary ActiveRecord object.
Then you may extend your Emial class with send_error_email method which sends an email if there was an error.
EDIT:
This is about new information you attached in comment.
You don't have to use nested ifs and switch here. You can have it looking like this:
def is_this_email_valid?
if !email_from_user_in_system?
#errors << :user_not_in_system
return false
end
if comment_not_exists?
#errors << :comment_not_exists
return false
end
if user_cannot_comment_here?
#errors << :permision_error
return false
end
...
true
end
Then you can use it:
if !#email.is_this_email_valid?
#email.send_error_mail
end
I suggest using Exceptions. Start with this tutorial, then use Google, trial and error to go from there.
Edit: In more complex cases, exceptions may not be the right tool. You might want to use validator functions instead, for example (see other answers), or you could just return early instead of nesting ifs, e.g.:
unless sender_valid?
#error = "Sender invalid"
return
end
unless subject_valid?
#error = "Invalid command"
return
end
# normal no-errors flow continues here...
You could throw an error when something is not right. Then catch it at the end of your method.
http://phrogz.net/programmingruby/tut_exceptions.html
To make your code more readable and not have a lot of switch and if/then statements, you could create separate methods that validate certain aspects and call them from your main error-checking method.
Is it possible to map your message to a model ? then all the if/switch logic would be validations and automatically handled by rails. Good starting point is active record validations guide
Also worth reading is action mailer guide

Am I abusing "rescue" for nil checks?

I use rescue for everything, not just for "rescuing" exceptions. I mean, I just love the way it spares me verification and double checking data.
By example, lets say I have a model Item what may or may not have a User. Then, when I want to get the owner's name of the item I write:
item.user.name rescue ""
instead of something like
item.user.nil? ? "" : item.user.name
It makes the same think, since nil.name trigger an exception that I rescue with "", but I'm not pretty sure this is good practice. It makes what I want to, and it makes it with less code, but... I don't know, all that rescue words here and there makes me feel insecure.
Is it a bad practice or is it valid abusing of the rescue keyword?
I think you are abusing rescue a bit, though in Rails, there is a specific method for these issues: try. Documentation
In your case, item.user.try(:name) might be a nicer approach.
I would say this is not really a good habit to get into. I've never really used this feature in Ruby since it feels like I'm just hiding error cases. It's also worth noting that you're rescuing any and all exceptions without specifying any type of expected error. It's just always looked like something which is going to make debugging down the road harder than it needs to be, although, like I've said, I've never bothered to use it myself.
Like in most other languages, making the check yourself will run faster than using rescue.
As an alternative to your rescue abuse, check out the andand gem. It's similiar to the try another posted suggested, but nicer. andand lets you say:
item.user.andand.name
The expression will be nil if item.user is nil.

Resources