1616use Illuminate \Contracts \Debug \ExceptionHandler ;
1717use Illuminate \Contracts \Http \Kernel as HttpKernel ;
1818use Illuminate \Foundation \Testing \Concerns \WithoutExceptionHandlingHandler ;
19+ use Illuminate \Http \Concerns \InteractsWithInput ;
1920use Illuminate \Http \Request ;
21+ use Illuminate \Http \UploadedFile ;
2022use Illuminate \Routing \UrlGenerator ;
2123use Illuminate \Support \Uri ;
2224use Pest \Browser \Contracts \HttpServer ;
2325use Pest \Browser \Exceptions \ServerNotFoundException ;
2426use Pest \Browser \Execution ;
2527use Pest \Browser \GlobalState ;
28+ use Pest \Browser \Http \RequestBodyParser ;
2629use Pest \Browser \Playwright \Playwright ;
2730use Psr \Log \NullLogger ;
31+ use Symfony \Component \HttpFoundation \File \UploadedFile as SymfonyUploadedFile ;
2832use Symfony \Component \Mime \MimeTypes ;
2933use Throwable ;
3034
@@ -50,14 +54,19 @@ final class LaravelHttpServer implements HttpServer
5054 */
5155 private ?Throwable $ lastThrowable = null ;
5256
57+ /**
58+ * A body parser for url-encoded and multipart requests
59+ */
60+ private RequestBodyParser $ requestBodyParser ;
61+
5362 /**
5463 * Creates a new laravel http server instance.
5564 */
5665 public function __construct (
5766 public readonly string $ host ,
5867 public readonly int $ port ,
5968 ) {
60- //
69+ $ this -> requestBodyParser = new RequestBodyParser ();
6170 }
6271
6372 /**
@@ -237,10 +246,9 @@ private function handleRequest(AmpRequest $request): Response
237246
238247 $ kernel = app ()->make (HttpKernel::class);
239248
240- $ contentType = $ request ->getHeader ('content-type ' ) ?? '' ;
241249 $ method = mb_strtoupper ($ request ->getMethod ());
242250 $ rawBody = (string ) $ request ->getBody ();
243- $ parameters = [] ;
251+ [ $ parameters, $ files ] = $ this -> requestBodyParser -> parseForm ( $ request , $ rawBody ) ;
244252 if ($ method !== 'GET ' && str_starts_with (mb_strtolower ($ contentType ), 'application/x-www-form-urlencoded ' )) {
245253 parse_str ($ rawBody , $ parameters );
246254 }
@@ -254,23 +262,13 @@ private function handleRequest(AmpRequest $request): Response
254262 $ method ,
255263 $ parameters ,
256264 $ cookies ,
257- [], // @TODO files...
265+ $ this -> convertUploadedFiles ( $ files),
258266 $ serverVariables ,
259267 $ rawBody
260268 );
261269
262270 $ symfonyRequest ->headers ->add ($ request ->getHeaders ());
263271
264- // Set the Host header to match the configured host for subdomain routing
265- $ configuredHost = Playwright::host ();
266- if ($ configuredHost !== null ) {
267- $ hostHeader = sprintf ('%s:%d ' , $ configuredHost , $ this ->port );
268- $ symfonyRequest ->headers ->set ('Host ' , $ hostHeader );
269- // Also set SERVER_NAME for Laravel routing
270- $ symfonyRequest ->server ->set ('SERVER_NAME ' , $ configuredHost );
271- $ symfonyRequest ->server ->set ('HTTP_HOST ' , $ hostHeader );
272- }
273-
274272 $ debug = config ('app.debug ' );
275273
276274 try {
@@ -312,6 +310,24 @@ private function handleRequest(AmpRequest $request): Response
312310 );
313311 }
314312
313+ /**
314+ * Taken from Laravel because we can't manipulate the test flag
315+ *
316+ * @param array<SymfonyUploadedFile[]|SymfonyUploadedFile> $files
317+ * @return array<UploadedFile[]|UploadedFile>
318+ *
319+ * @see InteractsWithInput
320+ */
321+ private function convertUploadedFiles (array $ files ): array
322+ {
323+ // @phpstan-ignore-next-line
324+ return array_map (function (array |SymfonyUploadedFile $ file ) {
325+ return is_array ($ file )
326+ ? $ this ->convertUploadedFiles ($ file )
327+ : UploadedFile::createFromBase ($ file , true );
328+ }, $ files );
329+ }
330+
315331 /**
316332 * Return an asset response.
317333 */
0 commit comments