Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ coverage.xml
# Playwright
node_modules/
/tests/Browser/Screenshots
/tests/Browser/Source

# MacOS
.DS_Store
10 changes: 10 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,14 @@ public function diff(): self

return $this;
}

/**
* Saves the page source when a browser assertion fails.
*/
public function source(): self
{
Playwright::setShouldSaveSourceOnFailedAssertions();

return $this;
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/BrowserExpectationFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Pest\Browser\Playwright\Page;
use Pest\Browser\Playwright\Playwright;
use Pest\Browser\ServerManager;
use Pest\Browser\Support\Source;
use PHPUnit\Framework\ExpectationFailedException;
use Throwable;

Expand All @@ -28,6 +29,16 @@ public static function from(Page $page, ExpectationFailedException $e): Expectat
if ($filename !== null) {
$message .= " A screenshot of the page has been saved to [Tests/Browser/Screenshots/$filename].";
}

if (Playwright::shouldSaveSourceOnFailedAssertions()) {
try {
$filename = Source::save($page->content());

$message .= " The source of the page has been saved to [Tests/Browser/Source/$filename].";
} catch (Throwable) {
// saving the source must never mask the original failure...
}
}
}

$consoleLogs = $page->consoleLogs();
Expand Down
2 changes: 2 additions & 0 deletions src/Filters/UsesBrowserTestCaseMethodFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Pest\Browser\ServerManager;
use Pest\Browser\Support\BrowserTestIdentifier;
use Pest\Browser\Support\Screenshot;
use Pest\Browser\Support\Source;
use Pest\Contracts\TestCaseMethodFilter;
use Pest\Factories\TestCaseMethodFactory;
use Pest\Plugins\Only;
Expand Down Expand Up @@ -59,6 +60,7 @@ public function accept(TestCaseMethodFactory $factory): bool

ServerManager::instance()->playwright()->start();
Screenshot::cleanup();
Source::cleanup();
}

return true;
Expand Down
21 changes: 21 additions & 0 deletions src/Playwright/Playwright.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ final class Playwright
*/
private static bool $shouldDiffOnScreenshotAssertions = false;

/**
* Whether to save the page source on failed assertions.
*/
private static bool $shouldSaveSourceOnFailedAssertions = false;

/**
* The default browser type.
*/
Expand Down Expand Up @@ -187,6 +192,22 @@ public static function shouldDebugAssertions(): bool
return self::$shouldDebugAssertions;
}

/**
* Set whether to save the page source on failed assertions.
*/
public static function setShouldSaveSourceOnFailedAssertions(): void
{
self::$shouldSaveSourceOnFailedAssertions = true;
}

/**
* Whether to save the page source on failed assertions.
*/
public static function shouldSaveSourceOnFailedAssertions(): bool
{
return self::$shouldSaveSourceOnFailedAssertions;
}

/**
* Reset playwright state, reset browser types, without closing them.
*/
Expand Down
78 changes: 78 additions & 0 deletions src/Support/Source.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Pest\Browser\Support;

use Pest\TestSuite;

/**
* @internal
*/
final class Source
{
/**
* Return the path to the sources' directory.
*/
public static function dir(): string
{
return TestSuite::getInstance()->rootPath
.'/tests/Browser/Source';
}

/**
* Return the full path for a source file.
*/
public static function path(string $filename): string
{
$filename = self::dir().'/'.mb_ltrim($filename, '/');

// check if there is extension, if not, add .html
if (pathinfo($filename, PATHINFO_EXTENSION) === '') {
$filename .= '.html';
}

return $filename;
}

/**
* Save the page source to the filesystem.
*/
public static function save(string $content, ?string $filename = null): string
{
if ($filename === null) {
// @phpstan-ignore-next-line
$filename = str_replace('__pest_evaluable_', '', test()->name());
}

if (is_dir(self::dir()) === false) {
@mkdir(self::dir(), 0755, true);
}

file_put_contents(self::path($filename), $content);

return $filename;
}

/**
* Clean up the sources directory.
*
* @codeCoverageIgnore
*/
public static function cleanup(): void
{
if (is_dir(self::dir()) === false) {
return;
}

$files = glob(self::dir().'/*');

if (is_array($files)) {
foreach ($files as $file) {
@unlink($file);
}
}

@rmdir(self::dir());
}
}
53 changes: 53 additions & 0 deletions tests/Browser/Webpage/SourceOnFailureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

use Pest\Browser\Playwright\Playwright;
use Pest\Browser\Support\Source;
use PHPUnit\Framework\ExpectationFailedException;

afterEach(function (): void {
$property = new ReflectionProperty(Playwright::class, 'shouldSaveSourceOnFailedAssertions');
$property->setValue(null, false);
});

it('may not save the page source when an assertion fails', function (): void {
Route::get('/', fn (): string => '<div>Hello World</div>');

$page = visit('/');

try {
$page->assertSee('Goodbye World');
} catch (ExpectationFailedException $exception) {
$filename = str_replace('__pest_evaluable_', '', test()->name());

expect($exception->getMessage())->not->toContain('The source of the page has been saved to')
->and(file_exists(Source::path($filename)))->toBeFalse();

return;
}

$this->fail('The assertion did not fail as expected.');
});

it('may save the page source when an assertion fails', function (): void {
Playwright::setShouldSaveSourceOnFailedAssertions();

Route::get('/', fn (): string => '<div id="content">Hello World</div>');

$page = visit('/');

try {
$page->assertSee('Goodbye World');
} catch (ExpectationFailedException $exception) {
$filename = str_replace('__pest_evaluable_', '', test()->name());

expect($exception->getMessage())->toContain("The source of the page has been saved to [Tests/Browser/Source/$filename].")
->and(file_exists(Source::path($filename)))->toBeTrue()
->and((string) file_get_contents(Source::path($filename)))->toContain('<div id="content">Hello World</div>');

return;
}

$this->fail('The assertion did not fail as expected.');
});
49 changes: 49 additions & 0 deletions tests/Unit/Support/SourceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

use Pest\Browser\Support\Source;

it('places sources under tests/Browser/Source', function (): void {
Source::save('<html></html>', 'test-source.html');

expect(file_exists(Source::path('test-source.html')))
->toBeTrue();
});

it('saves sources with .html extension when no extension is provided', function (): void {
Source::save('<html></html>', 'test-source');

expect(file_exists(Source::path('test-source.html')))
->toBeTrue();
});

it('saves sources with .html extension when no extension is provided and the filename starts with a slash', function (): void {
Source::save('<html></html>', '/test-source');

expect(file_exists(Source::path('test-source.html')))
->toBeTrue();
});

it('saves the given content as-is', function (): void {
Source::save('<html><body>Hello World</body></html>', 'test-source-content');

expect(file_get_contents(Source::path('test-source-content.html')))
->toBe('<html><body>Hello World</body></html>');
});

it('saves sources using the test name when no filename is given', function (): void {
$filename = Source::save('<html></html>');

expect($filename)->not->toContain('__pest_evaluable_')
->and(file_exists(Source::path($filename)))->toBeTrue();
});

it('cleans up the sources directory', function (): void {
Source::save('<html></html>', 'test-source-cleanup');

Source::cleanup();

expect(file_exists(Source::path('test-source-cleanup.html')))
->toBeFalse();
});