Errno::ENOENT: No such file or directory @ rb_file_s_mtime after feature tests

Something is likely off with how you're setting up the image that you're testing against. You might want to update that to how the Rails team handles it in their own tests:

module ActiveStorageHelpers
  # ported from https://github.com/rails/rails/blob/5-2-stable/activestorage/test/test_helper.rb#L57
  def create_file_blob(filename: "image.jpg", content_type: "image/jpeg", metadata: nil)
    ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
  end
end

RSpec.configure do |config|
  config.include ActiveStorageHelpers
end

Then place a tiny image file in spec/fixtures/file/images.jpg (that's where the file_fixture method will look for it).

With that in place, you can setup the image on your model in your feature test with something like:

instance_of_model.images.attach(create_file_blob)

The fact that the test passes just means that the test does not depend on that image being found. A broken image link will not generally cause a test to fail and it is in fact often the case that test writers do not even bother to supply mock images because it is (perceived to be) too much work and not relevant to the thing being tested.

See folks, this is why we ask for an example. Now that the OP has posted an example, we can see that his problem is browser caching, and will be solved by replacing

product.images.attach io: File.open(path), filename: 'image.png'

with

product.images.attach io: File.open(path), filename: "image-#{Time.now.strftime("%s%L")}.png"

in the helper that does the attachment. I would not have solved this without having the example.

Explanation

ActiveStorage handles saving and serving files. OP was saving and serving images with it, which is definitely what it was intended for. In order to allow for different services to serve files in different ways, ActiveStorage separates the published URL from the actual image serving URL.

The published URL is almost a permalink: it is an encoded version of the database key for the attachment. ActiveStorage processes a request for the URL by looking up where the file is stored and sending a 302 temporary redirect to the URL that can be used to access the file, called a "service URL". When using AWS S3 to store files, the service URL can be a signed URL that expires quickly but nevertheless connects the browser directly to S3 rather than having to go through the web server as intermediary.

By default, the service URL is good for 5 minutes, and ActiveRecord explicitly sets

Cache-Control: max-age=300, private

on the 302 redirect. The browser caches this redirect response and for the next 5 minutes the browser will not even attempt to use or verify the published URL, it will immediately replace the public URL with the cached service URL.

Unfortunately, while the public URL is predictably recreated, the service URL is randomly generated, so when the browser does its automatic redirection, the previously valid service URL no longer works. The solution (or workaround, depending on your point of view) is to distinguish the public URLs by including a timestamp in the filename, so that tests will not reuse public URLs.

Sidenote

By the way, you should not be using url_for with image_tag. Change your ERB from

= image_tag url_for(product.image.variant(resize: "120x120"))

to

= image_tag(product.image.variant(resize: "120x120"))

Rails is smart enough to handle either case, but the latter way is the recommended way.