From 6c4103f01f513ac3cb3ad97a32e51c28f80308c2 Mon Sep 17 00:00:00 2001 From: William Allen Date: Tue, 7 Jul 2026 11:32:55 -0400 Subject: [PATCH] Capture output images on test failure in CI We currently have many flaky tests. Laravel Dusk and Cypress both create output images on failure, but we currently have no way to access those when CI fails. This PR addresses that by uploading the images to CDash on failure. --- cypress.config.js | 11 +++++++++-- phpstan-baseline.neon | 18 ++++++++++++++++++ tests/BrowserTestCase.php | 25 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index 609ed12141..18f71537de 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -12,8 +12,10 @@ module.exports = defineConfig({ defaultCommandTimeout: 120000, retries: 2, e2e: { - setupNodeEvents() { - // implement node event listeners here + setupNodeEvents(on) { + on('after:screenshot', (details) => { + console.log(`\n${details.path}\n`); + }); }, baseUrl: 'http://localhost:8080', specPattern: 'tests/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', @@ -24,6 +26,11 @@ module.exports = defineConfig({ specPattern: 'tests/cypress/component/**/*.cy.{js,jsx,ts,tsx}', supportFile: 'tests/cypress/support/component.js', indexHtmlFile: 'tests/cypress/support/component-index.html', + setupNodeEvents(on) { + on('after:screenshot', (details) => { + console.log(`\n${details.path}\n`); + }); + }, devServer: { framework: 'vue', bundler: 'webpack', diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0f2383b713..b442feb3aa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -22521,6 +22521,24 @@ parameters: count: 1 path: tests/Browser/Pages/UsersPageTest.php + - + rawMessage: 'Call to internal method PHPUnit\Framework\TestCase::name() from outside its root namespace PHPUnit.' + identifier: method.internal + count: 1 + path: tests/BrowserTestCase.php + + - + rawMessage: 'Method Tests\BrowserTestCase::captureFailuresFor() has parameter $browsers with generic class Illuminate\Support\Collection but does not specify its types: TKey, TValue' + identifier: missingType.generics + count: 1 + path: tests/BrowserTestCase.php + + - + rawMessage: 'Parameter #1 $callback of method Illuminate\Support\Collection<(int|string),mixed>::each() expects callable(mixed, int|string): mixed, Closure(Laravel\Dusk\Browser, int): void given.' + identifier: argument.type + count: 1 + path: tests/BrowserTestCase.php + - rawMessage: Access to an undefined property CDash\Model\Build::$Endime. identifier: property.notFound diff --git a/tests/BrowserTestCase.php b/tests/BrowserTestCase.php index c14fd5e9af..8cd8b8c941 100644 --- a/tests/BrowserTestCase.php +++ b/tests/BrowserTestCase.php @@ -48,4 +48,29 @@ protected function driver(): RemoteWebDriver ) ); } + + /** + * Capture screenshots and console logs for failed tests. + * + * @param Collection $browsers + */ + protected function captureFailuresFor($browsers): void + { + $browsers->each(function (Browser $browser, int $key): void { + $name = str_replace('\\', '_', static::class) . '_' . $this->name(); + $screenshotName = 'failure-' . $name . '-' . $key; + $browser->screenshot($screenshotName); + $browser->storeConsoleLog($screenshotName); + + $screenshotPath = base_path("tests/Browser/screenshots/{$screenshotName}.png"); + if (file_exists($screenshotPath)) { + fwrite(STDOUT, "\n$screenshotPath\n"); + } + + $consoleLogPath = base_path("tests/Browser/console/{$screenshotName}.log"); + if (file_exists($consoleLogPath)) { + fwrite(STDOUT, "\n$consoleLogPath\n"); + } + }); + } }