friends-of-behat/mink-debug-extension
Behat extension that logs Mink-related data after every failed step and can save screenshots with a supporting driver. Configure a log directory, optional screenshots, and clean starts; especially useful for debugging CI runs like Travis.
This is a Behat extension, not a Laravel package — there is no service provider, facade, or config file under config/. It only does anything inside a Behat + Mink test suite. In a Laravel codebase that usually means a behat.yml at the project root with Behat\MinkExtension (Goutte/Chrome/Selenium) driving a locally booted app. If you're on PHPUnit/Pest or Dusk, this package adds nothing.
Minimal steps
require):
composer require --dev friends-of-behat/mink-debug-extension:^2.1
default:
extensions:
FriendsOfBehat\MinkDebugExtension:
directory: '%paths.base%/storage/logs/behat'
screenshot: true
If your Behat version doesn't resolve the short key, use the FQCN FriendsOfBehat\MinkDebugExtension\Extension.storage/logs/behat/)..html file in a browser.Where to look first
directory — it embeds the current URL, driver name, page source (and a base64 screenshot when enabled).behat.yml only. There is no other config surface: directory (required to enable the extension), screenshot (default false), clean_start (default true).Lakion\Behat\MinkDebugExtension — the namespace was renamed in v2.0 and logs moved from .log to .html.First use case
You have a failing Behat scenario (Then I should see "Order confirmed") in CI. Without the extension you get a one-line assertion error. With it you get an artifact you can open locally: the exact HTML the driver saw, the URL it was on, and (with a screenshot-capable driver) a picture of the page. That's the whole value proposition — post-mortem evidence for Mink failures.
Local vs CI profiles. Keep two profiles rather than toggling a single one, because there is no env-var interpolation for the screenshot/clean_start booleans:
default:
extensions:
FriendsOfBehat\MinkDebugExtension:
directory: '%paths.base%/storage/logs/behat'
screenshot: false
clean_start: false # keep history across runs while you're debugging
ci:
extensions:
FriendsOfBehat\MinkDebugExtension:
directory: '%paths.base%/storage/logs/behat'
screenshot: true
clean_start: true # fresh artifacts per pipeline
Run with vendor/bin/behat --profile=ci. In GitHub Actions/GitLab, upload storage/logs/behat/**/*.html (and screenshots if split out) with if: failure(), retention ~7 days.
Screenshot only where the driver supports it. screenshot: true is a no-op with Goutte/Zombie-style HTTP drivers — you still get the HTML log. Pair it with Selenium2/Chrome/docker-standalone when you actually need pixels, and set it false locally if you don't want the per-failure disk churn.
Combine with Laravel app bootstrapping. The most common Laravel setup boots the framework in-process and drives it with Goutte. To make the captured HTML useful, run Behat with APP_DEBUG=true and a dedicated .env.behat / APP_ENV=behat, so a 500 in your controller produces a Laravel exception page in the log instead of a bare "500 Internal Server Error". Cross-reference with storage/logs/laravel.log — the extension captures the browser-visible surface, not the server-side trace.
Workflow for triaging a failure.
vendor/bin/behat features/checkout.feature:42 --profile=ci..html in storage/logs/behat/.vendor/bin/behat --stop-on-failure -vvv if the DOM doesn't explain it.clean_start: false means they accumulate.Filling the gaps with your own hooks. The extension has no pluggable "log extra data" API — for Laravel-specific context (route, auth user ID, DB transaction state, Log::getMessages()), add a @AfterStep/@AfterScenario hook in a context that writes a sibling text file into the same directory:
/** @AfterStep */
public function dumpAppState(AfterStepScope $scope): void
{
if ($scope->getTestResult()->isPassed()) {
return;
}
file_put_contents(
sprintf('%s/step-%s.txt', $this->debugDir, uniqid()),
sprintf("route: %s\nuser: %s\n", request()->path(), auth()->id() ?? 'guest')
);
}
Keep it in the same directory so CI artifact globbing catches everything.
Complement, don't replace, assertions. Treat the log directory as disposable diagnostics. Don't add tests that parse these files, and don't gate a pipeline on their presence.
clean_start: true is destructive on the whole directory. Never point directory at storage/logs itself, public/, or anything holding real logs — on each Behat run the extension wipes it. Use a dedicated subdirectory.directory must be writable and %paths.base% resolves to the location of behat.yml, not the CWD. Running behat -c tests/behat/behat.yml shifts the base; relative paths then silently point elsewhere.BeforeScenario/AfterScenario hooks, and bootstrap/fatal errors usually generate nothing — that's a frequent "the extension is broken" false alarm. Reproduce with -vvv instead.-vvv and app logs when the failure is a wrong-URL problem.Lakion\Behat\MinkDebugExtension, .log files, or the removed upload-textfiles / upload-screenshots / wait-for-port binaries is v1-era and will not work on v2.behat --strict still registers the extension before committing to it. Budget for a fork if it stalls — the package is thinly maintained.vendor/bin/behat --config-reference or -vvv output listing loaded extensions.storage/logs/behat/ to .gitignore and to any Docker build context exclusions — stale artifacts otherwise leak into images and diffs.AfterStepTested. Plan for that rather than hunting for a config flag that doesn't exist.How can I help you explore Laravel packages today?