Moving RSpec in a Rails project from using Poltergeist to using Selenium (Webdriver) Chrome and now I'm getting some failures I wasn't quite expecting. Looks like it's trying to escape the URL for some reason? Thoughts?
Failures:
1) Story editing private story should be read after editing
Failure/Error: expect(story_cover_image).to eq "#{url}/convert?rotate=exif"
expected: "http://placehold.it/edited.png/convert?rotate=exif"
got: "\"http://placehold.it/edited.png/convert?rotate=exif\""
(compared using ==)
Here's the spec (removed what's not relevant):
feature 'Story editing', type: :feature, js: true do
...
let(:story_attributes) do
{
...
cover_image: {
url: 'http://placehold.it/edited.png'
}
}
end
...
def fill_file_url(url)
execute_script <<-JS
angular.element(".content").scope().story.addCoverImage();
angular.element(".content").scope().story.coverImage.url = "#{url}";
angular.element(".content").scope().story.save();
JS
expect(story_cover_image).to eq "#{url}/convert?rotate=exif"
end
...
def fill_in_story_form(story)
fill_file_url story[:cover_image][:url] if story[:cover_image]
end
...
context 'private' do
scenario 'story should be read after editing' do
fill_in_story_form story_attributes <<<<<< SPEC FAILS ON THIS LINE >>>>>
...
end
end
EDIT New failure after changing "#{url}" to #{url} as per request:
Also tried: angular.element(".content").scope().story.coverImage.url = url;
1) Story editing private story should be read after editing
Failure/Error:
execute_script <<-JS
angular.element(".content").scope().story.addCoverImage();
angular.element(".content").scope().story.coverImage.url = #{url};
angular.element(".content").scope().story.save();
JS
Selenium::WebDriver::Error::UnknownError:
unknown error: Runtime.evaluate threw exception: SyntaxError: Unexpected token :
(Session info: chrome=69.0.3497.100)
(Driver info: chromedriver=2.41.578706 (5f725d1b4f0a4acbf5259df887244095596231db),platform=Mac OS X 10.13.6 x86_64)
The problem is in this line:
angular.element(".content").scope().story.coverImage.url = "#{url}";
Because inside a HEREDOC everything is considered to be a string #{url} is all you need to do string interpolation. But in this case you want the interpolation to be treated as a Javascript string so you should use '#{url}'. Doing "#{url}" creates escaped double-quotes because by default a HEREDOC follows double-quoting rules, so you should change all your double quotes to single quotes inside the HEREDOC.
Trying it directly into the irb console you get:
url = "https://www.google.com"
<<-JS
angular.element('.content').scope().story.coverImage.url = '#{url}';
JS
=> "angular.element('.content').scope().story.coverImage.url = 'https://www.google.com';\n";
<<-JS
angular.element(".content").scope().story.coverImage.url = '#{url}';
JS
=> "angular.element(\".content\").scope().story.coverImage.url = 'http://www.google.com';\n"
Quoting rules of Heredoc docs
Related
I have an app that lists tickets. It uses AngularJS. Here's the controller action:
def index
#tickets = apply_scopes(#tickets)
response.headers['x-total-count'] = #tickets.total_count
response.headers['x-per-page'] = Ticket.default_per_page
end
The Angular controller (Coffeescript):
$scope.fetch = ->
Ticket.query($scope.search).then (response) ->
$scope.tickets = response.data
$scope.totalCount = response.headers('x-total-count')
$scope.perPage = response.headers('x-per-page')
$scope.fetch()
I'm using angular-rails-resource to fetch the records. Everything works smoothly if I test by hand.
Here is the spec:
let(:user) { create :user }
scenario 'User lists tickets', js: true do
login_as user, scope: :user
ticket = create :ticket, user: user
visit root_path
click_on 'Support Requests'
expect(page).to have_content(ticket.subject)
end
When I run this spec, I just get the regular Rspec failure message because the condition was not met, but it should have:
expected to find text "ticket 000" in...
I figured it had something to do with concurrency and Capybara not waiting for Angular to fetch and display the records. Then I went ahead and added a sleep 2 right above the expectation just to test that. When I do it, I get a different error:
Capybara::Poltergeist::JavascriptError:
One or more errors were raised in the Javascript code on the page. If you don't care about these errors, you can ignore them by setting js_errors: false in your Poltergeist configuration (see documentation for details).
Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "}
Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "}
at http://127.0.0.1:54674/assets/application-713835b1641be632b29f7502c00a879e171bca5d6d06a5f264afcd819c123e76.js:14363
Here is my stack:
rails (5.0.2)
capybara (2.12.1)
poltergeist (1.13.0)
rspec-core (3.5.4)
phantomjs 2.1.1
Additional info:
If I output something right before ending the controller action, it gets outputted. The execution is going through the entire action;
If I console.log something right before fetching tickets, it's outputted as well. However, the Promise is not being resolved.
I found out the issue was with my pagination headers (x-total-count and x-per-page). Converting them to String does the trick. The weird part is that it was working OK in development, but not in test environment. So, if anyone has this issue in the future, the solution in my case was:
def index
#tickets = apply_scopes(#tickets)
response.headers['x-total-count'] = #tickets.total_count.to_s
response.headers['x-per-page'] = Ticket.default_per_page.to_s
end
Notice .to_s being called when assigning the headers.
I have a spec file with an expectation that a controller action will return success.
The POST api/v1/users/:id/features/block action in the controller calls two HTTP calls on an external API, the only difference being in the body.
I've put the two requests and responses in the same VCR cassette, but when the cassette is being used, only the first request ever gets compared against and fails when it should be matching the second, causing the tests to fail.
What I'm looking for is a way of having the multiple requests match so the controller action completes and returns successfully.
The error I'm getting is at the end.
describe "POST /api/v1/users/:id/features/block" do
before(:each) do
#user = FactoryGirl.create(:user)
post :block, user_id: #user.id, block: "0"
end
it "should return 200 OK" do
expect(response).to be_success
end
end
Simplified versions of my VCR configuration and RSpec configuration follow:
VCR.configure do |c|
c.hook_into :webmock
c.default_cassette_options = {
match_requests_on: [:method, :uri, :body_regex]
}
c.register_request_matcher :body_regex do |request_1, request_2|
# Match body against regex if cassette body is "--ruby_regex /regexhere/"
if request_2.body[/^--ruby_regex\s*\//]
regex = request_2.body.gsub(/^--ruby_regex\s*\//, '').gsub(/\/$/, '')
request_1.body[/#{regex}/] ? true : false
else
true # No regex defined, continue processing
end
end
end
RSpec.configure do |c|
c.around(:each) do |example|
options = example.metadata[:vcr] || {}
name = example.metadata[:full_description].split(/\s+/, 2).join("/").underscore.gsub(/[^\w\/]+/, "_")
VCR.use_cassette(name, options, &example)
end
end
end
A summarized version of the cassette being used in this comparison that I'm having trouble with is:
---
http_interactions:
- request:
method: post
uri: https://upstream/api
body:
string: --ruby_regex /query1.+block/
response:
status:
code: 200
body:
string: { "response": "SUCCESS" }
- request:
method: post
uri: https://upstream/api
body:
string: --ruby_regex /query2.+block/
response:
status:
code: 200
body:
string: { "response": "SUCCESS" }
recorded_at: Fri, 05 Sep 2014 08:26:12 GMT
recorded_with: VCR 2.8.0
Error during tests:
An HTTP request has been made that VCR does not know how to handle
...
VCR is using the current cassette: (Correct cassette file path)
...
Under the current configuration VCR can not find a suitable HTTP interaction to replay and is prevented from recording new requests.
I don't want to record new requests because then the second one overwrites the first instead of adding the second request to the end of the cassette.
Trying to access this button on root url:
here is html
with this test:
feature "New comment button" do
scenario "User can add new comment on root page", :js => true do
visit root_path
id = 152
click_button("#button_#{id}")
within("#comment_row_#{id}") do
fill_in('content', :with => 'this is a comment')
click_button('create comment')
page.must_have_flash_message('Successfully created')
end
end
and geting this:
Capybara::ElementNotFound: Unable to find button "#button_152"
How to get this element using id ?
I am using selenium-web-driver
EDIT
WHAT I TRIED
# page.driver.browser.switch_to.frame 'top-frame' # Selenium::WebDriver::Error::NoSuchFrameError: Unable to locate frame: top-frame
# page.find('#button_152').click # not working
# click_button("#button_152") # not working
# first(:xpath, '//button[#id="button_152"]').click
2.This is an overview of frames :
all iframes are just google chrome addons
4.link to full html
You can read about switching windows and frames here: http://docs.seleniumhq.org/docs/03_webdriver.jsp#moving-between-windows-and-frames
Ruby specific bindings here: https://code.google.com/p/selenium/wiki/RubyBindings
Capybara handling iframes: handling iframe with capybara ruby
As for your problem, here's an example you can edit:
within_frame 'evernoteFilingTools' do
click_button("#button_#{id}")
#button_#{id} # not working
#page.find("#button_#{id}",:visible => true).click # does not work as well
within("#comment_row_#{id}") do
fill_in('content', :with => 'this is a comment')
click_button('create comment')
page.must_have_flash_message('Successfully created')
end
end
You should replace evernoteFilingTools with the iframe ID that contains the content you want to manipulate
Sometimes, it can be thrown off by invalid markup. This HTML has a div (<div class="icons">) inside a table, which is not valid. Try running the markup through a validator such as http://validator.w3.org/ and fix any errors it reports. That might fix your Capybara problem as well.
I meet an encoding problem... No errors in the console, but the output is not well encoded.
I must use Digest::SHA1.hexdigest on a string and then must pack the result.
The below example should outputs '{´p)ODýGΗ£Iô8ü:iÀ' but it outputs '{?p)OD?GΗ?I?8?:i?' in the console and '{�p)OD�G^BΗ�I�8^D�:i�' in the log file.
So, my variable called pack equals '{?p)OD?GΗ?I?8?:i?' and not '{´p)ODýGΗ£Iô8ü:iÀ'. That's a big problem... I'm doing it in a Rails task.
Any idea guys?
Thanks
# encoding: utf-8
require 'digest/sha1'
namespace :my_app do
namespace :check do
desc "Description"
task :weather => :environment do
hexdigest = Digest::SHA1.hexdigest('29d185d98c984a359e6e6f26a0474269partner=100043982026&code=34154&profile=large&filter=movie&striptags=synopsis%2Csynopsisshort&format=json&sed=20130527')
pack = [hexdigest].pack("H*")
puts pack # => {?p)OD?GΗ?I?8?:i?
puts '{´p)ODýGΗ£Iô8ü:iÀ' # => {´p)ODýGΗ£Iô8ü:iÀ
end
end
end
This is what I did (my conversion from PHP to Ruby)
# encoding: utf-8
require 'open-uri'
require 'base64'
require 'digest/sha1'
class Allocine
$_api_url = 'http://api.allocine.fr/rest/v3'
$_partner_key
$_secret_key
$_user_agent = 'Dalvik/1.6.0 (Linux; U; Android 4.2.2; Nexus 4 Build/JDQ39E)'
def initialize (partner_key, secret_key)
$_partner_key = partner_key
$_secret_key = secret_key
end
def get(id)
# build the params
params = { 'partner' => $_partner_key,
'code' => id,
'profile' => 'large',
'filter' => 'movie',
'striptags' => 'synopsis,synopsisshort',
'format' => 'json' }
# do the request
response = _do_request('movie', params)
return response
end
private
def _do_request(method, params)
# build the URL
query_url = $_api_url + '/' + method
# new algo to build the query
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = URI::encode(Base64.encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
return sig
end
end
Then call
allocine = Allocine.new(ALLOCINE_PARTNER_KEY, ALLOCINE_SECRET_KEY)
puts allocine.get('any ID')
get method return 'e7RwKU9E%2FUcCzpejSfQ4BPw6acA%3D' in PHP and 'cPf6I4ZP0qHQTSVgdKTbSspivzg=%0A' in Ruby...
thanks again
I think this "encoding" issue has turned up due to debugging other parts of a conversion from PHP to Ruby. The target API that will consume a digest of params looks like it will accept a signature variable constructed in Ruby as follows (edit: well this is guess, there may also be relevant differences between Ruby and PHP in URI encoding and base64 defaults):
require 'digest/sha1'
require 'base64'
require 'uri'
sig_data = 'edhefhekjfhejk8edfefefefwjw69partne...'
sig = URI.encode( Base64.encode64( Digest::SHA1.digest( sig_data ) ) )
=> "+ZabHg22Wyf7keVGNWTc4sK1ez4=%0A"
The exact construction of sig_data from the parameters that are being signed is also important. That is generated by the PHP method http_build_query, and I do not know what order or escaping that will apply to input params. If your Ruby version gets them in a different order, or escapes differently to PHP, the signature will be wrong (edit: Actually it is possible we are looking here for a signature on the exact query string sent the API - I don't know). It is possibly an issue of that sort that has led you down the rabbit hole of how the signature is constructed?
Thank you guys for your help.
Problem is solved. With the following code I obtain exactly the same string as with PHP:
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = CGI::escape(Base64.strict_encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
Now I've another problem for which I opened a new question here.
thanks you very much.
I have the following spec:
it "deletes post", :js => true do
...
...
page.status_code.should = '404'
end
The page.status_code line is giving me this error:
Capybara::NotSupportedByDriverError
How do I check the page's status code?
As an aside. This line
page.status_code.should = '404'
Should be
page.status_code.should == 404
This worked for me with capybara-webkit.
status_code is not currently supported by the Selenium driver. You will need to write a different test to check the response status code.
Either switch to another driver (like rack-test) for that test, or test that the displayed page is the 404 page (should have content 'Not Found' in h1).
As #eugen said, Selenium doesn't support status codes.
Selenium web driver doest not implement status_code and there is no direct way to test response_code with selenium (developer's choice).
To test it I added in my layout/application.html.erb:
<html code="<%= response.try(:code) if defined?(response) %>">[...]</html>
And then in my test:
def no_error?
response_code = page.first('html')[:code]
assert (response_code == '200'), "Response code should be 200 : got #{response_code}"
end
Try it out
expect(page).to have_http_status(200)
Use js to make a request and get status as below:
js_script = <<JSS
xhr = new XMLHttpRequest();
xhr.open('GET', '#{src}', true);
xhr.send();
JSS
actual.execute_script(js_script)
status = actual.evaluate_script('xhr.status') # get js variable value
Check https://gist.github.com/yovasx2/1c767114f2e003474a546c89ab4f90db for more details