Ruby Rspec cucumber array each do - ruby

I have scenario when upon login to the page, I am presented with numerous profile(can be 1 to 5).
I am looking for specific profile based by tn number.
I locate element that represent tn and then put in array to search for all available elements with same to locate correct profile in order to click on it.
Here is the code:
And(/^I look for "([^"]*)"$/) do |number|
elements = #driver.find_elements(:css => "h3.phone-number")
elements.each do |element|
renewals_page.select_profile.click if element.text == #config[number]
return element
end
fail
end
I am passing desired number from yaml file depends on the account.
renewals_page.select_profile.click is defined in another file as method
def select_profile
#driver.find_element(:css => "h3.phone-number")
end
So when I try to locate that element and click on it, I get following error
unexpected return (LocalJumpError)
./features/step_definitions/renewals_login_step.rb:28:in `block (2 levels) in <top (required)>'
./features/step_definitions/renewals_login_step.rb:26:in `each'
./features/step_definitions/renewals_login_step.rb:26:in `/^I look for "([^"]*)"$/'

Remove return from here:
renewals_page.select_profile.click if element.text == #config[number]
# ⇓⇓⇓⇓⇓⇓
# return element # removed
element # proper working return from lambda: result of last line
end
the reason is that return keyword may be used to return from method only, not from lambda.

Related

Ruby - no implicit conversion of Array into String

I am getting an error when executing my test.
Failure/Error: expect(industry_sic_code).to include page.sic_code
TypeError:
no implicit conversion of Array into String
# ./spec/os/bal/company/company_filter_clean_harbors_industries_stub.rb:62:in `block (2 levels) in <top (required)>'
The Method:
def sic_code
subtables = #b.table(:class => 'industry-codes').tables(:class => 'industry-code-table')
subtables.each do |subtable|
if subtable.tbody.h4.text == "US SIC 1987:"
subtable.tr.next_siblings.each do |tr|
codes = tr.cell
puts codes.text.to_s
end
end
end
end
The Test:
it 'Given I search for a random Clean Harbors Industry' do
#Pick a random clean industry from the file
data = CSV.foreach(file_path, headers: true).map{ |row| row.to_h }
random = data.sample
random_industry = random["Class"]
industry_sic_code = random["SIC Code"]
end
it 'Then the result has the expected SIC code' do
page = DetailPage.new(#b)
page.view
expect(industry_sic_code).to include page.sic_code
end
I have tried to implicitly change each variable to a string but it still complain about the array issue.
When I include some puts statments, I get some really wonky responses. The method itself returns the expected result.
When I used the method in the test I end up with the code gibberish below.
here are the sic codes from the method
5511
Here are the codes from the test
#<Watir::Table:0x00007fa3cb23f020>
#<Watir::Table:0x00007fa3cb23ee40>
#<Watir::Table:0x00007fa3cb23ec88>
#<Watir::Table:0x00007fa3cb23ead0>
#<Watir::Table:0x00007fa3cb23e918>
#<Watir::Table:0x00007fa3cb23e738>
#<Watir::Table:0x00007fa3cb23e580>
Your sic_code method returns subtables array, that's why you have this error. It doesn't matter that the method puts something, every method in ruby implicitly returns result of its last line, in your case it is subtables.each do ... end, so you have an array.
You need to explicitly return needed value. Not sure if I correctly understood what are you doing in your code, but try something like this:
def sic_code
subtables = #b.table(:class => 'industry-codes').tables(:class => 'industry-code-table')
result = [] # you need to collect result somewhere to return it later
subtables.each do |subtable|
if subtable.tbody.h4.text == "US SIC 1987:"
subtable.tr.next_siblings.each do |tr|
codes = tr.cell
result << codes.text.to_s
end
end
end
result.join(', ')
end

Getting server error 500 on calling a method ruby

When I call a function I get the following error log;
please help decipher it.
NoMethodError (undefined method `first' for #<Matching:0x0000000875a050>):
app/mailers/matching_mailer.rb:6:in `new_matchings_for_customer'
app/models/matching.rb:133:in `block in create_matchings_from_service'
app/models/matching.rb:126:in `each'
app/models/matching.rb:126:in `create_matchings_from_service'
app/models/matching.rb:30:in `process_matchings_for_service'
app/models/payments/subscription.rb:94:in `find_matchings'
app/models/payments/subscription.rb:85:in `after_create_actions'
app/controllers/contractors/subscriptions_controller.rb:51:in `subscribe'
app/controllers/contractors/subscriptions_controller.rb:19:in `create'
EDIT 1:
First few lines of matching mailer:
class MatchingMailer < ActionMailer::Base
default from: "\"Estimate My Project\" <info#estimatemyproject.com>"
def new_matchings_for_customer(matchings, customer_id)
#customer = Customer.find(customer_id)
#matchings = Matching.find(matchings)
#category = #matchings.first.emp_request.subcategory.category
unless #customer.email.empty?
mail(to: #customer.email, subject: "#{#category.name} estimate for project in #{#customer.zip_code.county.name}, #{#customer.zip_code.state.code} #{#customer.zip_code.code}")
else
self.message.perform_deliveries = false
end
end
NoMethodError (undefined method `first' for #<Matching:0x0000000875a050>)
means that there is no method first on a Matching.
app/mailers/matching_mailer.rb:6:in `new_matchings_for_customer'
means you try to call the method first on an instance of matching in line 6 of the `app/mailers/matching_mailer.rb``
Looking at your MatchingMailer in line 6, we see hat you call first on #matching. #matching was set just the line before. Please note that the Matching.find returns one record when you pass in a single id and returns an array of records when you pass in a array of ids. In this case you pass matchings that was provided as an argument to the new_matchings_for_customer method.
It is quite obvious, that the matchings argument must be a single id. Otherwise #matchings would have return an array and an array would respond to first. Since you always call first and never care about other values in the array, it makes more sense to just load one record.
Change your MatchingMailer to:
class MatchingMailer < ActionMailer::Base
default from: '"Estimate My Project" <info#estimatemyproject.com>'
def new_matchings_for_customer(matching_id, customer_id)
customer = Customer.find(customer_id)
if customer.email.present?
matching = Matching.find(matching_id)
category = matching.emp_request.subcategory.category
mail(
to: customer.email,
subject: "#{category.name} estimate for project in #{customer.zip_code.county.name}, #{customer.zip_code.state.code} #{customer.zip_code.code}"
)
else
self.message.perform_deliveries = false
end
end
end
And ensure to only pass one matching_id when calling that method.

Ruby - Parsing a string of a Hash using YAML - Error if hash entered raw and coerced to string rather than entered as string

I have a gem I have created that wraps Git as a key:value store (dictionary/hash). The source is here.
The way it works in the process referenced is as follows:
run the function set containing a key and a value argument
hash these with git, have the key point at the hash
return the key if this operation is successful and it is added to the global dictionary holing keys and hashes
Now, if I call something like
db.set('key', {some: 'value'})
# => 'key'
and then try to retrieve this,
db.get('key')
Psych::SyntaxError: (<unknown>): did not find expected node content while parsing a flow node at line 1 column 2
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:370:in `parse'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:370:in `parse_stream'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:318:in `parse'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:245:in `load'
from /home/bobby/.rvm/gems/ruby-2.2.1/gems/gkv-0.2.1/lib/gkv/database.rb:21:in `get'
from (irb):6
from /home/bobby/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
Now, if I set the key as that same dictionary, but as a string:
db.set('key', "{some: 'value'}")
# => 'key'
db.get('key')
# => {"key"=>"value"}
db.get('key').class
=> Hash
The operation that is performing the git operations' and wrapping them to a kv store source is:
...
def get(key)
if $ITEMS.keys.include? key
YAML.load(Gkv::GitFunctions.cat_file($ITEMS[key].last))
else
raise KeyError
end
end
def set(key, value)
update_items(key, value.to_s)
key
end
...
And the get_items function being referenced here's source is:
...
def update_items(key, value)
if $ITEMS.keys.include? key
history = $ITEMS[key]
history << Gkv::GitFunctions.hash_object(value.to_s)
$ITEMS[key] = history
else
$ITEMS[key] = [Gkv::GitFunctions.hash_object(value.to_s)]
end
end
end
...
hash_object and cat_object simple wrap git hash-object and git cat-file in a method writing the input to a tmpfile, git adding it, and then erasing the tempfile.
I'm really at a loss as to why this works with strings but not true dictionaries. It results in the exact same error if you use the old hashrocket syntax as well:
db.set('a', {:key => 'value'})
=> "a"
db.get('a')
# => Psych::SyntaxError: (<unknown>): did not find expected node content while parsing a flow node at line 1 column 2
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:370:in `parse'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:370:in `parse_stream'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:318:in `parse'
from /home/bobby/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/psych.rb:245:in `load'
from /home/bobby/.rvm/gems/ruby-2.2.1/gems/gkv-0.2.1/lib/gkv/database.rb:21:in `get'
from (irb):6
from /home/bobby/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
Any ideas?
In your get method you call YAML.load, but in your set method you use .to_s. This means that the YAML parser is trying to read an arbitrary string as if it were YAML. For symmetry YAML.dump should be used in the set method instead.
I've created a pull request with the changes.

RSpec 2 and 3 How to create a helper that will generate a bunch of examples?

I'm trying to create helper to test JSON responses in uniform and nice way.
For more descriptive and specific failures I want to generate one example per JSON atom.
Example syntax:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it_conforms_to_json(
id: 27,
email: "john.smith#example.com",
name: "John",
profile_description: %r{professional specialist},
nested: {
id: 37,
status: "rejected"
}
)
end
end
So this snippet will be an equivalent to:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it 'has equal value at object["id"]' do
expect(subject["id"]).to eq(27)
end
it 'has equal value at object["email"]' do
expect(subject["email"]).to eq("john.smith#example.com")
end
it 'has equal value at object["name"]' do
expect(subject["name"]).to eq("John")
end
it 'has matching value at object["profile_description"]' do
expect(subject["profile_description"]).to match(%r{professional specialist})
end
it 'has equal value at object["nested"]["id"]' do
expect(subject["nested"]["id"]).to eq(37)
end
it 'has equal value at object["nested"]["status"]' do
expect(subject["nested"]["status"]).to eq("rejected")
end
end
end
I was able to achieve that easily with this snippet:
module JsonHelper
extend self
def it_conforms_to_json(json)
generate_examples_for(json)
end
private
def generate_examples_for(json, opts)
with_prefix = opts.fetch(:with_prefix, [])
if json.is_a?(Hash)
json.each do |key, new_json|
new_prefix = with_prefix + [key.to_s]
generate_examples_for(new_json, opts.merge(with_prefix: new_prefix))
end
elsif json.is_a?(Array)
raise NotImplemented.new("Arrays are not allowed yet")
elsif json.is_a?(String) || json.is_a?(Numeric)
it "is expected to have equal value at json[\"#{with_prefix.join('"]["')}\"]" do
value = JSON.parse(subject)
with_prefix.each { |key| value = value[key.to_s] }
expect(value).to eq(json)
end
end
end
end
And just enabling it by extending it: rspec_config.extend JsonHelper
Obvious problem starts when you think about pretty failure messages:
They show backtrace including location of it "is expected...bla-bla-bla
They exclude location of it_conforms_to_json(...) call
First is fixable by adding backtrace exclusion/clean pattern, but it results in a full backtrace because everything is filtered.
Second and previous is fixable by wrapping expect statement in begin..rescue, mangling with backtrace by prepending it with file_path:line of it_conforms_to_json(...) call and re-raising modified exception.
After first 2 problems are being resolved, the new one arises:
"Unable to find matching line from the backtrace"
Suspected method is find_first_non_rspec_line (or something similar), that finds first line in a backtrace of it call (not exception backtrace) by applying default rspec exclusion regex'es.
It is fixable by mangling with RSpec internals, i.e.:
For rspec 2: monkey patch method that applies this regex'es
For rspec 3: undefine constant LIB_REGEX and define it again adding this helper files to this regex
For rspec 3 (recent minor/patch versions): the same, but with IGNORE_REGEX
Code of this fixup becomes shitty and unreadable, and it will be hell to maintain, because it depends on rspec inner implementation, that can change from minor/patch version to version. Who interested in reading this ugly thing here
More that that, it acts differently for rspec 2 and rspec 3.
rspec 2, perfectly what is required:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: it_conforms_to_json(
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
2) A json response is expected to have equal value at json["name"]
Failure/Error: it_conforms_to_json(
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
rspec 3, slightly off, but still acceptable:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: expect(value).to eq(json)
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
2) A json response is expected to have equal value at json["name"]
Failure/Error: expect(value).to eq(json)
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
So here are the questions:
Is there any built-in public-API means of achieving it?
It seems I have problems with naming here, it is not expectations, but what...?
So, it is a bit ugly now (first iteration, though), but works. No mangling with RSpec internals, using only public RSpec API, with nice error messages, without even cleaning a backtrace:
module RSpec
module JsonExpectations
class JsonTraverser
def self.traverse(errors, expected, actual, prefix=[])
if expected.is_a?(Hash)
expected.map do |key, value|
new_prefix = prefix + [key]
if actual.has_key?("#{key}")
traverse(errors, value, actual["#{key}"], new_prefix)
else
errors[new_prefix.join("/")] = :no_key
false
end
end.all?
elsif expected.is_a?(String) || expected.is_a?(Numeric)
if actual == expected
true
else
errors[prefix.join("/")] = {
actual: actual,
expected: expected
}
false
end
else
raise NotImplementedError, "#{expected} expectation is not supported"
end
end
end
end
end
RSpec::Matchers.define :include_json do |expected|
match do |actual|
unless expected.is_a?(Hash)
raise ArgumentError, "Expected value must be a json for include_json matcher"
end
RSpec::JsonExpectations::JsonTraverser.traverse(
#include_json_errors = {},
expected,
JSON.parse(actual)
)
end
# RSpec 2 vs 3
send(respond_to?(:failure_message) ?
:failure_message :
:failure_message_for_should) do |actual|
res = []
#include_json_errors.each do |json_path, error|
res << %{
json atom on path "#{json_path}" is missing
} if error == :no_key
res << %{
json atom on path "#{json_path}" is not equal to expected value:
expected: #{error[:expected].inspect}
got: #{error[:actual].inspect}
} if error.is_a?(Hash) && error.has_key?(:expected)
end
res.join("")
end
end
It allows this syntax now:
it "has basic info about user" do
expect(subject).to include_json(
id: 37,
email: "john.smith#example.com",
name: "Smith"
)
end
It generates errors like this:
F
Failures:
1) A json response has basic info about user
Failure/Error: expect(subject).to include_json(
json atom on path "id" is not equal to expected value:
expected: 37
got: 25
json atom on path "name" is not equal to expected value:
expected: "Smith"
got: "John"
# ./spec/simple_with_fail_spec.rb:11:in `block (2 levels) in <top (required)>'
Finished in 0.00065 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/simple_with_fail_spec.rb:10 # A json response has basic info about user
Works for both RSpec 2 and 3.
If somebody interested it is here https://github.com/waterlink/rspec-json_expectations

How to pull an item from the set of elements using capybara with rails

I've tried to refactor the old piece of tests. The code below:
describe "translation result", :js => true do
it "translations should be shown as links to translations in second way" do
visit '/'
fill_in('query-field', :with => 'kOŃ')
click_button('search-button')
sleep(7)
page.all(:css, '.result a').size.should eq(2)
page.find('.result a').text.should == 'horse'
end
end
return information below:
1) translation result translations should be shown as links to translations in second way
Failure/Error: page.find('.result a').text.should == 'horse'
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching css ".result a"
# ./spec/integration/result_spec.rb:12:in `block (2 levels) in <top (required)>'
I tried to get a element from what is returned by 'find method' in describe block. It means I tried to do it as below shown:
page.find('.result a').first.text.should == 'horse'
or
page.find('.result a')[0].text.should == 'horse'
I did it because I think when I got two elements then I can get one of them. Do you think my logic is correct ? How to resolve the problem. The repo with code is here: https://github.com/mtczerwinski/dict-app
You can just use all in place of find if you really want to find multiple matches:
page.all('.result a').first.text.should == 'horse'
See http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders#all-instance_method

Resources