Skip to content

Commit 769a85d

Browse files
authored
Merge pull request #175 from spatie/image-assertions
Add image assertions
2 parents e26433d + 9b2cd8b commit 769a85d

File tree

10 files changed

+261
-1
lines changed

10 files changed

+261
-1
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ To make snapshot assertions, use the `Spatie\Snapshots\MatchesSnapshots` trait i
9797
- `assertMatchesTextSnapshot($actual)`
9898
- `assertMatchesXmlSnapshot($actual)`
9999
- `assertMatchesYamlSnapshot($actual)`
100+
- `assertMachesImageSnapshot($actual)`
100101

101102
### Snapshot Testing 101
102103

@@ -180,6 +181,17 @@ The `assertMatchesFileHashSnapshot($filePath)` assertion asserts that the hash o
180181

181182
The `assertMatchesFileSnapshot($filePath)` assertion works almost the same way as the file hash assertion, except that it actually saves the whole file in the snapshots directory. If the assertion fails, it places the failed file next to the snapshot file so they can easily be manually compared. The persisted failed file is automatically deleted when the test passes. This assertion is most useful when working with binary files that should be manually compared like images or pdfs.
182183

184+
### Image snapshots
185+
186+
The `assertImageSnapshot` requires the [spatie/pixelmatch-php](https://github.com/spatie/pixelmatch-php) package to be installed.
187+
188+
This assertion will pass if the given image is nearly identical to the snapshot that was made the first time the test was run. You can customize the threshold by passing a second argument to the assertion. Higher values will make the comparison more sensitive. The threshold should be between 0 and 1.
189+
190+
```php
191+
$this->assertMatchesImageSnapshot($imagePath, 0.1);
192+
```
193+
194+
183195
### Customizing Snapshot Ids and Directories
184196

185197
Snapshot ids are generated via the `getSnapshotId` method on the `MatchesSnapshot` trait. Override the method to customize the id. By default, a snapshot id exists of the test name, the test case name and an incrementing value, e.g. `Test__my_test_case__1`.

build/report.junit.xml

Lines changed: 117 additions & 0 deletions
Large diffs are not rendered by default.

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
"symfony/serializer": "^5.2|^6.2",
3131
"symfony/yaml": "^5.2|^6.2"
3232
},
33+
"require-dev": {
34+
"spatie/pixelmatch-php": "dev-main",
35+
"spatie/ray": "^1.37"
36+
},
3337
"autoload": {
3438
"psr-4": {
3539
"Spatie\\Snapshots\\": "src"
@@ -53,4 +57,5 @@
5357
"dev-v5": "5.0-dev"
5458
}
5559
}
60+
5661
}

src/Drivers/HtmlDriver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public function serialize($data): string
1515
throw new CantBeSerialized('Only strings can be serialized to html');
1616
}
1717

18-
if ('' === $data) {
18+
if ($data === '') {
1919
return "\n";
2020
}
2121

src/Drivers/ImageDriver.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Spatie\Snapshots\Drivers;
4+
5+
use PHPUnit\Framework\Assert;
6+
use PHPUnit\Framework\ExpectationFailedException;
7+
use Spatie\Pixelmatch\Exceptions\CouldNotCompare;
8+
use Spatie\Pixelmatch\Pixelmatch;
9+
use Spatie\Snapshots\Driver;
10+
11+
class ImageDriver implements Driver
12+
{
13+
public function __construct(
14+
protected float $threshold = 0.1,
15+
protected bool $includeAa = true,
16+
) {
17+
}
18+
19+
public function serialize($data): string
20+
{
21+
return file_get_contents($data);
22+
}
23+
24+
public function extension(): string
25+
{
26+
return 'png';
27+
}
28+
29+
public function match($expected, $actual)
30+
{
31+
$tempPath = sys_get_temp_dir();
32+
33+
$expectedTempPath = $tempPath.'/expected.png';
34+
file_put_contents($expectedTempPath, $expected);
35+
36+
$actualTempPath = $tempPath.'/actual.png';
37+
file_put_contents($actualTempPath, $actual);
38+
39+
$pixelMatch = Pixelmatch::new($expectedTempPath, $actualTempPath)
40+
->threshold($this->threshold)
41+
->includeAa($this->includeAa);
42+
43+
try {
44+
$result = $pixelMatch->matches();
45+
} catch (CouldNotCompare $exception) {
46+
throw new ExpectationFailedException($exception->getMessage());
47+
}
48+
49+
Assert::assertTrue($result);
50+
}
51+
}

src/MatchesSnapshots.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Spatie\Snapshots\Concerns\SnapshotDirectoryAware;
88
use Spatie\Snapshots\Concerns\SnapshotIdAware;
99
use Spatie\Snapshots\Drivers\HtmlDriver;
10+
use Spatie\Snapshots\Drivers\ImageDriver;
1011
use Spatie\Snapshots\Drivers\JsonDriver;
1112
use Spatie\Snapshots\Drivers\ObjectDriver;
1213
use Spatie\Snapshots\Drivers\TextDriver;
@@ -111,6 +112,17 @@ public function assertMatchesYamlSnapshot($actual): void
111112
$this->assertMatchesSnapshot($actual, new YamlDriver());
112113
}
113114

115+
public function assertMatchesImageSnapshot(
116+
$actual,
117+
float $threshold = 0.1,
118+
bool $includeAa = true
119+
): void {
120+
$this->assertMatchesSnapshot($actual, new ImageDriver(
121+
$threshold,
122+
$includeAa,
123+
));
124+
}
125+
114126
/*
115127
* Determines the directory where file snapshots are stored. By default a
116128
* `__snapshots__/files` directory is created at the same level as the
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Spatie\Snapshots\Test\Unit\Drivers;
4+
5+
use PHPUnit\Framework\ExpectationFailedException;
6+
use PHPUnit\Framework\TestCase;
7+
use Spatie\Snapshots\Drivers\ImageDriver;
8+
9+
class ImageDriverTest extends TestCase
10+
{
11+
public function setUp(): void
12+
{
13+
parent::setUp();
14+
15+
$this->driver = new ImageDriver();
16+
17+
$this->pathToImageA = __DIR__.'/../test_files/testA.png';
18+
$this->pathToImageB = __DIR__.'/../test_files/testB.png';
19+
$this->pathToImageWithDifferentDimensions = __DIR__.'/../test_files/testC.png';
20+
21+
}
22+
23+
/** @test */
24+
public function it_can_serialize_an_image()
25+
{
26+
$data = $this->driver->serialize($this->pathToImageA);
27+
28+
$this->assertEquals($data, file_get_contents($this->pathToImageA));
29+
}
30+
31+
/** @test */
32+
public function it_can_determine_that_two_images_are_the_same()
33+
{
34+
$this->driver->match(
35+
file_get_contents($this->pathToImageA),
36+
file_get_contents($this->pathToImageA),
37+
);
38+
39+
$this->doesNotPerformAssertions();
40+
}
41+
42+
/** @test */
43+
public function it_can_determine_that_two_images_are_not_same()
44+
{
45+
$this->expectException(ExpectationFailedException::class);
46+
47+
$this->driver->match(
48+
file_get_contents($this->pathToImageA),
49+
file_get_contents($this->pathToImageB),
50+
);
51+
}
52+
53+
/** @test */
54+
public function it_will_determine_that_two_images_with_different_dimensions_are_different()
55+
{
56+
$this->expectException(ExpectationFailedException::class);
57+
58+
$this->driver->match(
59+
file_get_contents($this->pathToImageA),
60+
file_get_contents($this->pathToImageWithDifferentDimensions),
61+
);
62+
}
63+
}

tests/Unit/test_files/testA.png

149 KB
Loading

tests/Unit/test_files/testB.png

180 KB
Loading

tests/Unit/test_files/testC.png

462 KB
Loading

0 commit comments

Comments
 (0)