skip to content

When a Laravel Dusk test fails only in CI, which artefacts does Dusk leave behind and how do you use them to find the cause?

level: middleimportance: nice to knowfreq 20%

answer

  1. failure-<test>-<n>.png
  2. tests/Browser/console logs
  3. tests/Browser/source on source asserts
  4. headless unless --browse outside CI
  5. dusk:chrome-driver --detect

basics

~20 s

On failure Dusk saves a screenshot per browser to tests/Browser/screenshots; after every browse() it writes non-empty console logs to tests/Browser/console, plus page source after source assertions. Keep them as CI artefacts and read them before changing code.

solid answer

~50 s

`browse()` catches a failure and saves `failure-<test name>-<index>.png` for each open browser in `tests/Browser/screenshots`, first resizing the window to fit the content unless `disableFitOnFailure()` was called. After every `browse()` call, pass or fail, it writes the Chrome console log to `tests/Browser/console` when there are entries (favicon noise is filtered), and on failure, if the test made a source assertion, it saves the page source to `tests/Browser/source`. In CI, keep those directories as job artefacts. The screenshot shows what the browser saw (an error page, a validation message, an unexpanded menu), the console shows JavaScript errors and failed requests, and the source shows the rendered HTML. Common CI-only causes: the app not being served at `APP_URL`, a ChromeDriver version that no longer matches Chrome (fix with `php artisan dusk:chrome-driver --detect`), a smaller headless window hiding elements, and missing waits on a slower machine.

code

bash · 5 lines
bash
php artisan dusk:chrome-driver --detect
./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
php artisan serve --no-reload &
php artisan dusk
# on failure, keep tests/Browser/screenshots and tests/Browser/console

go deeper

for a junior

Remember that failing Dusk tests leave screenshots in tests/Browser/screenshots and console logs in tests/Browser/console.

for a middle

Explain when each artefact is written, how fitContent affects screenshots, and how to add manual screenshots at key steps.

for a senior

Build CI jobs that serve the app, install a matching ChromeDriver, build assets and upload artefacts, then classify failures from them before touching code.

for a principal

Make browser-test failures cheap to diagnose by standardising artefact collection and environment setup across every pipeline that runs Dusk.

## Why Dusk failures need artefacts A Dusk failure message, for example "Waited 5 seconds for selector [@welcome-banner]", says what the test expected, not what the browser saw. In CI there is no visible browser to look at. Dusk therefore saves **artefacts** automatically, and the fastest diagnosis is to read them first. ## What Dusk saves and when The base `Laravel\Dusk\TestCase` sets three directories in `setUp()`, and `browse()` fills them: | Artefact | Directory | When | |---|---|---| | Screenshot `failure-<test>-<n>.png` | `tests/Browser/screenshots` | the closure throws; one per open browser | | Console log `<test>-<n>.log` (JSON) | `tests/Browser/console` | after every `browse()` call, if the console has entries | | Page source `<test>-<n>.txt` | `tests/Browser/source` | on failure, if the test made a source assertion | Details worth knowing: - Before the failure screenshot, Dusk calls `fitContent()` to resize the window to the page, so the image shows the whole page. `$browser->disableFitOnFailure()` turns that off. - Console logs are collected for Chrome, and messages about `favicon.ico` are ignored. - You can save artefacts yourself at any step: `screenshot('after-submit')`, `screenshotElement('@date-picker', 'picker')`, `responsiveScreenshots('signup')`, `storeConsoleLog('signup')` and `storeSource('signup')`. - `php artisan dusk:purge` clears old artefacts. In the CI job, upload `tests/Browser/screenshots` and `tests/Browser/console` as artefacts on failure; the Dusk docs' CI examples do exactly that. ## Reading them 1. **Screenshot first.** A Laravel error page means a server-side exception: check the app log. A validation message means the test data is wrong for CI (an email already taken because of database state). A half-rendered page, with widgets missing, points to a JavaScript error or a timing issue. 2. **Console next.** A JavaScript error or a failed network request explains a widget that never appeared. 3. **Source last.** It shows the HTML the browser actually rendered, useful when an element exists but is hidden. ## The usual CI-only causes - **The app is not reachable.** Dusk does not start a web server. CI must start one (commonly `php artisan serve --no-reload &`) and set `APP_URL` to it, typically `http://127.0.0.1:8000`. - **ChromeDriver and Chrome drift apart.** CI images update Chrome, and a driver pinned in the repository stops matching, so the session fails to start. Run `php artisan dusk:chrome-driver --detect` in the job to install the matching driver. - **Headless differences.** The generated `DuskTestCase` runs `--headless=new` with a 1920x1080 window. `php artisan dusk --browse` shows a real window locally, but only outside CI, since the command ignores `--browse` when a `CI` variable is set. - **Frontend assets not built.** Without `npm run build` (and with no Vite dev server running), the `@vite` directive cannot find the build manifest and throws `ViteManifestNotFoundException`, so the screenshot shows an error page instead of the form. - **Timing.** A slower runner exposes missing waits; replace `pause()` with condition waits. - **Environment files.** `.env.dusk.{environment}` is swapped in by `php artisan dusk`; make sure the CI environment's file exists or the variables are set directly. ## A short procedure 1. Download the artefacts from the failed job. 2. Classify: server error, data problem, build problem, driver problem or timing. 3. Reproduce locally with `php artisan dusk --browse` or `dusk:fails`. 4. Fix the cause, not the symptom: raising a timeout rarely fixes a JavaScript error.

  • The CI job fails before any test runs, saying the browser session could not be created. What is the likely cause?
    The ChromeDriver binary no longer matches the Chrome version installed on the CI image, usually after the image updated Chrome. Add `php artisan dusk:chrome-driver --detect` to the job so it installs the driver that matches the detected Chrome before running `php artisan dusk`.
  • The failure screenshot shows the page without any of its JavaScript-driven widgets. What do you check?
    Open the saved console log in `tests/Browser/console`: a JavaScript error or a failed request is the usual culprit, for example a script that throws because an API call returned 500. Fix that error, then add a condition wait so the test does not race the widgets' rendering on slower runners.

saying these in an interview costs you the question

  • Dusk only saves a screenshot if the test calls screenshot() itself
  • Console logs are written only when a test fails
  • The --browse flag opens a visible browser even on CI runners
  • Raising every wait timeout is the standard fix for CI-only failures
  • Dusk starts the application server automatically in CI