Laravel Dusk does not document a built-in API for setting Chrome’s download directory or asserting that a download has finished. Dusk supplies browser orchestration and actions; the download itself must be configured at the Chrome/ChromeDriver layer, then verified by checking a filesystem that the test process can actually see. The exact preference or capability syntax depends on your Laravel Dusk, php-webdriver, Chrome and ChromeDriver versions, so verify that combination before committing a configuration snippet.
This guide shows a version-safe test design, the checks that make headless downloads reliable in CI, and the limits of what the official documentation promises.
What Dusk provides—and what it does not
Laravel Dusk’s documentation describes browser automation with Google Chrome and a standalone ChromeDriver by default. It also documents an alternative Selenium-compatible arrangement. Dusk’s attach method is for uploading a local file to an HTML file input; it is not a download API.
The same documentation includes headless Chrome examples for continuous-integration environments. Those examples establish how to run Chrome headlessly, not how to choose a download directory or wait for a completed artifact. Treat download handling as an integration between three separate pieces:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
- SLIM. LIGHTWEIGHT. READY TO GO: The all-new slim design is perfect for busy lives on the go.
- SKILLFULLY DESIGNED. MILITARY TOUGH: Built with premium craftsmanship to withstand the occasional drop or ding.
- ALL-DAY, ALL-IN-ONE CHARGING: Power through your school day – and beyond – with a long-lasting 12-hour battery.¹
- 3X FASTER THAN THE PREVIOUS GENERATION OF WIFI: Crush your schoolwork in record time with Wi-Fi that’s three times faster than the previous generation of Wi-Fi.
- YOUR PHONE AND CHROMEBOOK WORK BETTER TOGETHER: Easily transfer files between devices, and control your phone right from your Chromebook.
- Dusk: starts the browser session and clicks the application’s download control.
- Chrome/ChromeDriver: decides where downloaded bytes are written and whether prompts are suppressed.
- Your test process: waits for a completed file and validates it.
This separation matters when ChromeDriver is remote or runs in a container: a file on the browser host is not automatically present on the PHP runner.
Prerequisites and version alignment
- Identify the exact versions. Record Laravel, Dusk, php-webdriver, Chrome or Chromium, and ChromeDriver in local and CI logs.
- Install a compatible driver. Laravel’s documentation shows
php artisan dusk:chrome-driver --detectfor installing a driver matching detected Chrome/Chromium on the local operating system. - Match current ChromeDriver distribution rules. The official ChromeDriver documentation says Chrome and ChromeDriver releases from milestone 115 onward are distributed through Chrome for Testing channels and provides JSON endpoints for automated downloads. Do not assume an old “download the driver binary” command remains valid.
- Use a writable, isolated directory. Create a fresh directory per test or worker, owned by the user running Chrome. Never point parallel tests at one shared directory unless you also use unique filenames and locking.
- Decide where the browser runs. If the driver is remote, arrange an artifact-copy step or a shared volume. A PHP assertion against the runner’s local path cannot see a browser-container path by magic.
The current Laravel 13.x introduction recommends Pest 4 browser testing for new projects. That is a dated documentation recommendation, not a requirement to migrate an existing Dusk suite.
Configure the download location at the driver boundary
Chrome normally asks where to save files in interactive mode. Headless tests need a predetermined destination and download prompts disabled. However, the reviewed Dusk and ChromeDriver pages do not publish one universal PHP method or capability array for this setting. APIs differ between Dusk releases and the underlying php-webdriver version.
Before using any code copied from a blog or issue, verify all of the following against your declared versions:
Free tools Windows power users keep installed
One-click scans. No signup required.
- Whether Dusk lets you customize the Chrome options object through the driver factory or a custom driver.
- The exact Chrome preference key and value type expected by that version.
- Whether the preference is applied to the browser that Dusk actually starts in CI.
- Whether a remote Selenium service accepts the preference and writes to a mounted directory.
Keep that version-specific setup in one small driver/bootstrap component. The rest of the test can then use a stable filesystem contract: DOWNLOAD_DIR exists, is writable, and contains only files created by the current test.
Rank #2
- FOR HOME, WORK, & SCHOOL – With an Intel processor, 14-inch display, custom-tuned stereo speakers, and long battery life, this Chromebook laptop lets you knock out any assignment or binge-watch your favorite shows..Voltage:5.0 volts
- HD DISPLAY, PORTABLE DESIGN – See every bit of detail on this micro-edge, anti-glare, 14-inch HD (1366 x 768) display (1); easily take this thin and lightweight laptop PC from room to room, on trips, or in a backpack.
- ALL-DAY PERFORMANCE – Reliably tackle all your assignments at once with the quad-core, Intel Celeron N4120—the perfect processor for performance, power consumption, and value (2).
- 4K READY – Smoothly stream 4K content and play your favorite next-gen games with Intel UHD Graphics 600 (3) (4).
- MEMORY AND STORAGE – Enjoy a boost to your system’s performance with 4 GB of RAM while saving more of your favorite memories with 64 GB of reliable flash-based eMMC storage (5).
A reliable Dusk test pattern
The following example deliberately separates the browser action from completion detection. It assumes your verified driver configuration maps DOWNLOAD_DIR to Chrome’s download directory. The application route and filename pattern are examples; replace them with your real endpoint and expected artifact.
<?php
namespace TestsBrowser;
use LaravelDuskBrowser;
use TestsDuskTestCase;
class DownloadReportTest extends DuskTestCase
{
public function test_report_download_completes(): void
{
$directory = storage_path('app/dusk-downloads/' . uniqid('report-', true));
if (! is_dir($directory) && ! mkdir($directory, 0775, true) && ! is_dir($directory)) {
$this->fail("Cannot create download directory: {$directory}");
}
if (! is_writable($directory)) {
$this->fail("Download directory is not writable: {$directory}");
}
// Your verified Dusk/ChromeDriver bootstrap must configure Chrome to use
// this same directory before the browser session is created.
putenv('DOWNLOAD_DIR=' . $directory);
try {
$this->browse(function (Browser $browser) use ($directory) {
$browser->visit('/reports')
->assertSee('Download report')
->click('@download-report');
$file = $this->waitForCompletedFile($directory, 60);
$this->assertNotFalse($file, 'No completed download appeared within 60 seconds.');
$this->assertSame('report.csv', basename($file));
$this->assertGreaterThan(0, filesize($file));
$this->assertStringContainsString('id,', file_get_contents($file));
});
} finally {
$this->removeDirectory($directory);
}
}
private function waitForCompletedFile(string $directory, int $timeout): string|false
{
$deadline = microtime(true) + $timeout;
do {
$partial = glob($directory . '/*.{crdownload,part,tmp}', GLOB_BRACE) ?: [];
$files = array_values(array_filter(glob($directory . '/*') ?: [], 'is_file'));
if (!$partial && count($files) === 1) {
$candidate = $files[0];
clearstatcache(true, $candidate);
$size = filesize($candidate);
usleep(250000);
clearstatcache(true, $candidate);
if ($size !== false && $size === filesize($candidate)) {
return $candidate;
}
}
usleep(250000);
} while (microtime(true) < $deadline);
return false;
}
private function removeDirectory(string $directory): void
{
foreach (glob($directory . '/*') ?: [] as $file) {
if (is_file($file)) @unlink($file);
}
@rmdir($directory);
}
}
This pattern avoids treating a click or navigation as proof that bytes arrived. It also avoids a single short sleep, which can pass on a fast laptop and fail under CI load. The temporary-extension check is a practical guard, while the size-stability check catches a file that is still being written. Your application may use a different temporary suffix, so inspect actual artifacts in a failing run and adjust the allowlist.
Headless execution in CI
Run the same browser mode locally and in CI where possible. Laravel’s CI guidance demonstrates headless Chrome and starting the Laravel server; use it as the process model, not as a download recipe. A dependable pipeline should:
- Start the application at the host and port Dusk expects.
- Start the intended ChromeDriver and print its version.
- Print Chrome’s version and the resolved download directory.
- Ensure the directory is writable by the Chrome user inside the container or VM.
- Collect the directory as a CI artifact when the test fails.
- Clean the directory after each test so an old file cannot satisfy a new assertion.
If Chrome runs in a separate container, mount a named volume at the browser-side download path and mount that volume into the PHP test container, or copy the artifact through your test infrastructure. Do not silently change the assertion to a runner-local path that Chrome cannot access.
Diagnose failures systematically
No file appears
- Confirm the click reached the real download control rather than a disabled button or a JavaScript error.
- Check that the browser session used the expected headless ChromeDriver, not a locally running browser with different preferences.
- Inspect Chrome’s host filesystem and verify the configured directory is absolute, exists and is writable.
- Check authentication, CSRF protection and response status for the download endpoint.
The test sees a partial file
Increase the timeout, recognize the temporary extension used by your Chrome build, and wait for both disappearance of the partial file and stable size. A fixed delay alone is not a completion signal.
Rank #3
- Storage: 16GB Flash Memory
- OS: Chrome OS
- Screen Size: 11.6"
The file exists but the assertion cannot find it
Compare browser-host and PHP-host paths. In a remote setup, inspect the remote filesystem or configure shared storage. Also check whether the server supplied a dynamic filename; assert a controlled pattern or parse the response headers in an application-level test.
ChromeDriver fails to start
Log both versions and replace the driver using the current Chrome for Testing channel guidance. Re-run php artisan dusk:chrome-driver --detect only where the detected local browser is the browser you intend to test; it does not solve a mismatched remote container automatically.
The download is a PDF or opens in a tab
A response with an inline content disposition may render instead of downloading. Test the application’s actual download response, including its Content-Disposition behavior, and do not infer a file download merely because a URL changed.
Performance, isolation and reliability
- Use one directory per test or worker to eliminate stale-file races.
- Prefer an explicit server-side filename for deterministic assertions.
- Set a timeout based on your largest expected artifact and CI latency; record elapsed time on failure.
- Validate content, not only existence. A zero-byte file or an HTML error page saved with a CSV extension is still a failed download.
- Keep browser and driver versions pinned or intentionally updated together, then review failures after every browser-image change.
- For large files, consider testing authorization and response headers separately from a full browser transfer; Dusk is most valuable for proving the user-visible click path.
When Dusk is not the right new-project choice
Laravel’s current documentation recommends Pest 4 browser testing for new projects, while Dusk remains a documented option and is often the least disruptive choice for an established Dusk suite. Decide using your framework compatibility, existing tests, driver-management policy, CI topology and whether you can verify download behavior on the exact browser image you deploy. Neither official source presents a download-specific feature comparison, so do not choose on the assumption that one framework supplies a hidden download API.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is simply to obtain a clean image or PDF of a URL rather than test your application’s user download flow, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, with the result identified by response headers.
See the parameter reference in the ScreenshotNeo documentation. cURL:
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteRank #4
- Intel Celeron N4120: 4 Cores & Threads, 1.1GHz Base Clock, Up to 2.6GHz Boost Clock, 4MB Cache, Intel UHD Graphics 600. The perfect combination of performance, power consumption, and value helps your device handle multitasking smoothly and reliably with four processing cores to divide up the work.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also has an MCP server so Claude, Cursor and other MCP clients can call take_screenshot, get_page_info and capture_pdf. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can Dusk wait for a browser download with a built-in assertion?
The Laravel Dusk guide reviewed here does not document a download-completion assertion. Implement completion detection at the filesystem boundary and verify any helper against your installed versions.
Where is a downloaded file stored when ChromeDriver is remote?
It is stored on the browser host unless your Selenium or container setup provides shared storage. Make that path visible to the PHP process or transfer the artifact before asserting it.
Does headless mode itself guarantee downloads are enabled?
No. Headless execution and download preferences are separate concerns. Configure the browser for your exact ChromeDriver stack and test the real download action.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

