Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

theone's avatar

File upload test with pest v4

Test.php

        $fakeFile = base_path('tests/Browser/files/file.pdf');
        $page->attach('form.docs.0.file', $fakeFile)->wait(5);

When i run this it showing the preview of the uploaded file , when it submits the form in backend it is not receiving the uploaded file , Seems $wire.upload() is not triggered . How can i trigger the wire:model change ?

1 like
3 replies
LaryAI's avatar
Level 58

Certainly! The core issue here is that when you use Alpine.js to handle file input changes and preview the file, Livewire's wire:model is not automatically triggered unless the file input itself changes and is managed by Livewire.

In your component, you are using $wire.upload(...) inside Alpine's handleFileDrop, but this only works if the component is a Livewire component and the file is passed correctly. However, when you use browser automation (like Laravel Dusk or Pest's browser testing), the file is attached to the input, but Alpine's handler may intercept and not trigger the native input event Livewire expects.

Solution:

  1. Ensure the file input is NOT wire:ignore
    If you use wire:ignore on the parent, Livewire will not listen for changes on the input. Remove wire:ignore from the wrapping <div> if you want Livewire to handle file uploads.

  2. Let Livewire handle the file input
    The easiest way is to let Livewire's wire:model handle the file input directly, and only use Alpine.js for preview.
    Your input should look like this:

    <input type="file"
           class="fileInput"
           accept="{{ $accept ?? '*' }}"
           wire:model="{{ $attributes->wire('model')->value() }}"
           x-on:change="handleFileDrop($event)"
           name="{{ $name }}"
    />
    
    • Do not wrap the parent in wire:ignore unless you have a very good reason.
  3. In your test, use the correct input name
    If your input is named form.docs.0.file, then your test should be:

    $fakeFile = base_path('tests/Browser/files/file.pdf');
    $page->attach('input[name="form.docs.0.file"]', $fakeFile);
    

    or if using Pest's browser testing:

    $browser->attach('input[name="form.docs.0.file"]', $fakeFile);
    
  4. If you must use Alpine's $wire.upload
    Make sure that the $wire.upload is called with the correct property name, and that the file is a real JavaScript File object (which it should be in browser tests).

    But for most use-cases, letting Livewire handle the file input is simpler and more reliable.


Example: Minimal Working Setup

Blade:

<input type="file"
       wire:model="file"
       x-on:change="handleFileDrop($event)"
       name="file"
/>

Livewire Component:

public $file;

public function save()
{
    $this->validate([
        'file' => 'required|file|mimes:pdf',
    ]);

    $path = $this->file->store('uploads');
    // ...
}

Test:

$fakeFile = base_path('tests/Browser/files/file.pdf');
$page->attach('input[name="file"]', $fakeFile)
     ->press('Submit');

Summary

  • Remove wire:ignore from the parent if present.
  • Let Livewire handle the file input via wire:model.
  • Use the correct input selector in your test.
  • Alpine.js can still be used for preview, but do not override Livewire's file handling.

If you follow these steps, your file should be received by Livewire on form submission, and your tests will pass. If you need to trigger wire:model manually, you can dispatch an input event on the file input, but this is rarely necessary if the above is followed.

SayHey's avatar

I am having the same issue when trying to test a FileUpload component from Filament v4.

Test:

...

$page->fill('input#form\\.name.fi-input', 'New Customer Name')
            ->attach("input.filepond--browser[type='file']", base_path('tests/Browser/assets/profile_picture.png'))
            ->assertSee('Uploading file...')
            ->wait(20)
            ->click('Save changes')
            ->assertSee('Saved')
            ->click('.fi-icon-btn.fi-no-notification-close-btn')
            ->assertDontSee('Saved')
            ->screenshot();

...

In my case Filament even throws an error about something going wrong and i get a stacktrace from livewire:

Doing it directly in playwright works when using the filechooser but from what i understand pest directly attaches the file to the HTML input element (not sure about this):

let fileChooserPromise = page.waitForEvent('filechooser');
await page.getByText('Drag & Drop your files or').click();
let fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, '../../assets/profile_picture.png'));

I have switched to testing the file uploads via livewire, instead of pest just to get moving, but i would love to have the possibility to do it with pest.

Livewire test:

function setAvatar(TestCase $test): void
{
    $filename = 'customer_avatar.png';
    $file = UploadedFile::fake()->image($filename);

    Livewire::test(EditCustomerProfile::class, [
        'tenant' => $test->customer,
        'record' => $test->customer->id,
    ])
        ->fillForm([
            'name' => $test->customer->name,
            'avatar_url' => $file,
        ])
        ->call('save')
        ->assertHasNoFormErrors();

    Storage::disk('local')->assertExists($test->customer->avatar_url);
}

I am not going to actively continue investigating the issue, just adding some context in case someone else stumbles upon this in the future.

Please or to participate in this conversation.