Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActionController::ParameterMissing when file is attached #174

Open
simmerz opened this issue Nov 22, 2021 · 4 comments
Open

ActionController::ParameterMissing when file is attached #174

simmerz opened this issue Nov 22, 2021 · 4 comments

Comments

@simmerz
Copy link

simmerz commented Nov 22, 2021

Without the file attached, I get validation errors (as expected), but as soon as I attach a file, I get no errors on the console suggesting that's gone wrong, but I get a failed submit with the following error in RSpec:

     ActionController::ParameterMissing:
       param is missing or the value is empty: guide
       Did you mean?  action
                      controller

Removing the validation on the file attribute and not filling it in proves that the submit is successful otherwise.

I'm using a remote chrome docker instance using a browserless.io chrome image

@simmerz
Copy link
Author

simmerz commented Nov 22, 2021

Further to this, it appears that uploading and attaching files via the browserless workspace API works fine, but doesn't if the app directory is bind mounted in to the browserless container. Using VSCode devcontainers on macOS Monterey, so could be anything in the way that directories are bind mounted too causing this issue.

Here's a helper to allow remote uploads to work:

# frozen_string_literal: true

require 'net/http/post/multipart'
require 'mime/types'

# Browserless Helpers
module BrowserlessHelpers
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
  def attach_remote_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
    raise ArgumentError, '`#attach_file` does not support passing both a locator and a block' if locator && block_given?

    remote_paths = []

    Array(paths).each do |path|
      raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s)

      # Upload local files to browserless.io instance
      remote_paths << browserless_file(path)
    end
    remote_paths = remote_paths.first if remote_paths.one?

    options[:allow_self] = true if locator.nil?

    if block_given?
      begin
        execute_script Capybara::Node::Actions::CAPTURE_FILE_ELEMENT_SCRIPT
        yield
        file_field = evaluate_script 'window._capybara_clicked_file_input'
        raise ArgumentError, "Capybara was unable to determine the file input you're attaching to" unless file_field
      rescue ::Capybara::NotSupportedByDriverError
        warn 'Block mode of `#attach_remote_file` is not supported by the current driver - ignoring.'
      end
    end
    # Allow user to update the CSS style of the file input since they are so often hidden on a page
    if make_visible
      ff = file_field || find(:file_field, locator, **options.merge(visible: :all))
      while_visible(ff, make_visible) { |el| el.set(remote_paths) }
    else
      (file_field || find(:file_field, locator, **options)).set(remote_paths)
    end
  end
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity

  private

  def browserless_file(path)
    request = Net::HTTP::Post::Multipart.new('/workspace',
                                             file: UploadIO.new(path.to_s,
                                                                mime_type(path)))
    uri = URI.parse(ENV['CHROME_URL'])
    response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(request) }
    json = JSON.parse(response.body, symbolize_names: true).first
    json[:path]
  end

  def mime_type(path)
    MIME::Types.type_for(path.to_s).first.content_type
  end
end

RSpec.configure do |config|
  config.include BrowserlessHelpers, type: :system
end

Replace instances of attach_file with attach_remote_file

@route
Copy link
Member

route commented Nov 25, 2021

@simmerz There are various capybara tests regarding file attachment:

be rspec --example="#attach_file"

Google Chrome 96.0.4664.45 unknown

Capybara starting Puma...
* Version 4.3.10 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:36639
Run options: include {:full_description=>/\#attach_file/}

Capybara::Session Cuprite
  #attach_file
    with normal form
      should set a file path by id
      should set a file path by label
      should be able to set on element if no locator passed
      casts to string
    with multipart form
      should set a file path by id
      should set a file path by label
      should not break if no file is submitted
      should send content type text/plain when uploading a text file
      should send content type image/jpeg when uploading an image
      should not break when uploading a file without extension
      should not break when using HTML5 multiple file input
      should not break when using HTML5 multiple file input uploading multiple files
      should not send anything when attaching no files to a multiple upload field
      should not append files to already attached
      should fire change once when uploading multiple files from empty
      should fire change once for each set of files uploaded
    with a locator that doesn't exist
      should raise an error
    with a path that doesn't exist
      should raise an error
    with :exact option
      should set a file path by partial label when false
      should not allow partial matches when true
    with :make_visible option
      applies a default style change when true
      accepts a hash of styles to be applied
      raises an error when the file input is not made visible
      resets the style when done
      should fire change
    with a block
      can upload by clicking the file input
      can upload by clicking the label
      should fire change

Finished in 8.06 seconds (files took 1.3 seconds to load)
28 examples, 0 failures

and all of them pass. You are describing application specific issue and I'm glad to help, but you should at least to provide the failing test in isolation (example dummy app) or try to reproduce the issue inside cuprite tests. Without it it's hard to take a look at BrowserlessHelpers and say where the issue is...

@simmerz
Copy link
Author

simmerz commented Nov 25, 2021

It actually looks like it's a specific issue when the app codebase is bind mounted inside a docker container, rather than a specific problem with cuprite - I'm using VSCode devcontainers here. There are similar issues when attaching files via FactoryBot or directly using fixture_file_upload too, and the solution in those instances is to make a copy of the file first so it's native to the container and then attach that.

The BrowserlessHelpers module is effectively just a remote upload to the workspace API in Browserless, so that Chrome can attach the file natively without needing to be bind mounted to the app folder. It's a working option.

The reason for the failure isn't in that code I pasted - more that was a means of sharing one solution if others were suffering it

@route
Copy link
Member

route commented Nov 28, 2021

In general now it's not clear even to me what's going on because there are a lot of missing details regarding the spec as well as test setup. I don't clearly see your solution either. So I'm afraid currently it's all mixed up and doesn't provide any particular help to the others. If you could restructure your description from top to bottom that would be helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants