twalpole / apparition

Capybara driver for Chrome using CDP
MIT License
363 stars 46 forks source link

Ajax request returns empty string #40

Closed henrik closed 4 years ago

henrik commented 4 years ago

Changing an old project from PhantomJS to Apparition.

We're seeing a failing test in Apparition where an Ajax request often returns an empty-string success response instead of a JSON success response. But sometimes returns the correct response.

It passes with PhantomJS, and the code works in Chrome in development.

We stripped the problematic spec down to something smaller:

it "foo", :js do
  visit root_path
  sleep 1  # Ensure jQuery has loaded
  click_link "My foo link"
  expect(page).to have_content("AJAX done")
end

The link is:

<a class="js-foo" href="#">My foo link</a>

With some jQuery code in CoffeeScript:

$ ->

  $(".js-foo").click (e) ->
    e.preventDefault()
    console.log("clicked")
    $.get
      url: "/foo"
      success: (a,b,c) ->
        console.log("GET success via jQuery", JSON.stringify(a),JSON.stringify(b), JSON.stringify(c))
        $(document.body).append("<p>AJAX done</p>")
      error: (a,b,c) -> console.log("error", a,b,c)

The /foo endpoint is:

def foo
  puts "about to return 123"
  render json: { number: 123 }
end

What we're seeing is that most of the time, the spec says

log: clicked
about to return 123
log: GET success via jQuery "" "success" {"readyState":4,"responseText":"","status":200,"statusText":"OK"}

So it fires the Ajax request, the controller says "about to return 123", but jQuery only sees an empty-string response text.

Sometimes (maybe once in 5-10 spec runs or so?) it does what we wanted, saying

log: clicked
about to return 123
log: GET success via jQuery {"number":123} "success" {"readyState":4,"responseText":"{\"number\":123}","responseJSON":{"number
":123},"status":200,"statusText":"OK"}

If we add dataType: "json" to the jQuery AJAX request, it also fails sometimes and passes sometimes, with slightly different messages, but it seems to amount to the same thing – it gets an empty-string response sometimes, which then can't be parsed as JSON.

We used jQuery in this simplified example, but the real project uses AngularJS's $http (it's an old project that we're modernising, starting with the tests), and runs into the same issue, so I'm guessing this applies to all Ajax requests.

Any ideas what could be going on here, that could cause Ajax requests to respond with an empty string, even though the server seems to provide the correct response?

carlosantoniodasilva commented 4 years ago

I believe I'm experiencing the same behavior trying to move to apparition from poltergeist/phantomjs.

We have some tests that seem to fail most of the time, and every once in a while they seem to pass randomly. So far I've noticed the same thing: an empty string is what the "success" callback is getting, instead of the expected data from the response. (this is with jQuery.)

I'll try to dig some more and see if I can pinpoint anything, but any help or direction would be appreciated.

twalpole commented 4 years ago

The most helpful thing would be a self contained example that shows the behavior

carlosantoniodasilva commented 4 years ago

Thanks @twalpole, here's one shot at that trying to repro that: https://gist.github.com/carlosantoniodasilva/171002ec38d2700c17060d221638bc47.

I haven't been able to spend more time diving into it, will have to circle back on it later this week, but let me know if I can help provide any other info in the meantime.

DangerDawson commented 4 years ago

I am witnessing a similar behaviour as well

DangerDawson commented 4 years ago

So after spending time trying to understand if there was some kind of pattern, I managed to mitigate the issue, by visiting another page first e.g. root_path first before visiting the page which implemented the Ajax request.

I could also achieve the same by using a sleep 10 before visiting the page which implemented the Ajax request, but that was a less desirable approach.

Hence I ended up putting a hook in place for all my :js specs to run the following (I am using Hanami):

before do
  visit Web.routes.root_path
end
deepj commented 4 years ago

The same issue here :( I switched from selenium to apparition.

The problem rarely occurs while fetching API via Fetch API in choices.js selectboxes

shepmaster commented 4 years ago

I am also seeing the same. I haven't been able to reduce it yet, but do have some debugging output. Wireshark shows that my endpoint is called twice, but that it does return the proper JSON:

GET /logs/recordings.json HTTP/1.1
Host: 127.0.0.1:59166
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/javascript, */*; q=0.01
X-CSRF-Token: XAFMPJ9inL8nhCEghXyIhYTiHAqyC1CaKCi7TGWCstrNX5rddkP349tF322fdooioZMebLphFSEuf/ypKSOv/A==
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.61 Safari/537.36
X-Requested-With: XMLHttpRequest
Cookie: _session_id=aa29ad90e9e8ad4201a6867093e87573
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:59166/jobs/1
Accept-Encoding: gzip, deflate, br

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-cache, no-store
Content-Type: application/json; charset=utf-8
ETag: W/"f33a6af83c402b05e546195c2bfb6c65"
X-Request-Id: c606dd8c-e152-4ade-9bda-dee8d65fe272
X-Runtime: 0.023983
Transfer-Encoding: chunked

{"recordings":[]}
GET /logs/recordings.json HTTP/1.1
Host: 127.0.0.1:59166
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/javascript, */*; q=0.01
X-CSRF-Token: XAFMPJ9inL8nhCEghXyIhYTiHAqyC1CaKCi7TGWCstrNX5rddkP349tF322fdooioZMebLphFSEuf/ypKSOv/A==
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.61 Safari/537.36
X-Requested-With: XMLHttpRequest
Cookie: _session_id=aa29ad90e9e8ad4201a6867093e87573
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:59166/jobs/1
Accept-Encoding: gzip, deflate, br

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-cache, no-store
Content-Type: application/json; charset=utf-8
ETag: W/"f33a6af83c402b05e546195c2bfb6c65"
X-Request-Id: 7e618fe6-121b-4524-9768-5dd09d715710
X-Runtime: 0.031293
Transfer-Encoding: chunked

{"recordings":[]}

Adding some debugging to jQuery, I see that it thinks that the data received is empty:

console.log(status, statusText, JSON.stringify(responses));
log: 200 OK {"text":""}
shepmaster commented 4 years ago

@twalpole I see that @carlosantoniodasilva created a reproduction case; is that what you were looking for, or would you like it modified some? I know your time is sparse, so if you have a bit to point us in a direction, we can help reduce it further if needed.

timherby commented 4 years ago

We are seeing this too with version 0.5.0, and it also happens with 0.4.0. The AJAX response is simply coming back empty. Would love to use this new driver, but we have a single page app that relies heavily on AJAX. Looking forward to someone taking the plunge and fixing this. Wish we had the time right now.

twalpole commented 4 years ago

This is somehow caused by the https://github.com/twalpole/apparition/blob/master/lib/capybara/apparition/page.rb#L533 handler - trying to figure out why

twalpole commented 4 years ago

Please try the master branch - it fixes the reproduction case that was provided - hopefully fixes everyones code too

carlosantoniodasilva commented 4 years ago

@twalpole thanks so much! I confirmed the reproduction script I had created before passes now on master as of a16c0f894ee1bb5f33475b848ceb1a33846b1e01.

Our app tests aren't all passing though simply pointing to master, but I think it might have something to do with the client side validations we have in place now. I'm not entirely clear why they were passing before, there's something else going on, I'll see if I can pinpoint and/or create another repro script. I think it's unrelated to this though, I'll let other also chime in once they've been able to test it out.

carlosantoniodasilva commented 4 years ago

It looks like what I was hitting is the same as https://github.com/twalpole/apparition/issues/33, a "blur" event not triggering on input, only after the button is clicked, so JS validations were preventing the form from being submitted. Not suggesting or asking to change that behavior here, I can workaround it for now on our side to get all the tests working, but it confirms it's unrelated to the issue in question here and the fix on master seems to have fixed it. Thanks again @twalpole.