# ExSnappy
GetSnappy is a visual regression testing tool, ExSnappy is used to generate HTML for rendering by GetSnappy.
## Installation
```elixir
def deps do
[
{:ex_snappy, "~> 0.1.0"}
]
end
```
## Configuration
There's currently a little bit of ceremony to get the service running, due to a couple of quirks in how testing works for components and LiveView in the testing environment.
When a live view is rendered with the `live(...)` function, you get back the full HTML, which includes the necessary `<head>` content which loads your CSS.
Unfortunately if you use any of the `render_x(...)` calls, you only get back a subset of the HTML, similarly if you use `render_component` you get the HTML from the individual component, but none of the HTML responsible for loading styles.
Currently the way to deal with this is to provide `wrapper_fn` function in `test.exs`. In future we aim to provide a more ergonomic way using your application's templates directly.
This is essentially the contents of your `root.html.heex`, plus the content of `app.html.heex`.
**NOTE** Don't add references to load JavaScript. The reason for this is you don't really want to connect the websocket and have a second page render or deal with things like the 'topbar', which will cause you to have inconsistent images due to timing issues.
```elixir
config :ex_snappy,
wrapper_fn: fn inner_content ->
"""
<!DOCTYPE html>
<html lang="en" class="[scrollbar-gutter:stable]">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link phx-track-static rel="stylesheet" href="/assets/app.css" />
</head>
<body class="bg-white antialiased dark:bg-gray-900 dark:text-gray-100">
<main>
#{inner_content}
</main>
</body>
</html>
"""
end
```
If you're running tests locally, you likely don't want to be running the visual regression tests, so we only execute if you explicitly enable things. Suggested way of dealing with this below
```elixir
config :ex_snappy,
enabled: System.get_env("EX_SNAPPY_ENABLED") == "true"
```
# Setup
To execute the visual regression tests, you'll need to install the binary from https://get-snappy.com/app/downloads
And add some configuration data by creating a `.get-snappy.yml` file
```yml
local_dist_dir: "priv/static"
snappy_workers: 4
test_command: "mix do assets.build, mix test"
browsers: ["chrome"]
```
You'll also need to set some environment variables, an example of this is below, but you'd likely get the COMMIT and BRANCH from your CI provider.
```shell
COMMIT=$(git rev-parse HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
export GET_SNAPPY_API_KEY="your-project-api-ket"
export GET_SNAPPY_COMMIT=$COMMIT
export GET_SNAPPY_BRANCH=$BRANCH
```
Finally, execute the tests you'd run something like this (further information on `EX_SNAPPY_ENABLED` below):
`EX_SNAPPY_ENABLED=true ./get-snappy snapshot-run`
## Usage
The `snap` macro can be used in three forms. See `ExSnappy` module for further details.
Key steps:
1. `import ExSnappy` to make the `snap` macro available
2. Use the `snap` function to capture screenshots. **NOTE** Screenshot names are automatically generated based on the name of the `test` function, but if you take multiple screenshots in a single test, you'll need to provide an explicit name to be appended to the test name.
```elixir
defmodule MyApp.LiveIndexTest do
use MyApp.ConnCase, async: true
import Phoenix.LiveViewTest
import ExSnappy
end
```
Then to use in tests, you need send HTML. This can be achieved by calling `render` only your `live` process, passing the `html` generated from a `live()` call, or the `html` generated from the `render_component` function.
### Rendering from `live()`
```elixir
defmodule MyApp.LiveIndexTest do
use MyApp.ConnCase, async: true
import Phoenix.LiveViewTest
import ExSnappy
describe "my tests" do
test "List projects and project builds", %{conn: conn, project: project} do
# Visit top level builds page
{:ok, index_live, html} = live(conn, ~p"/app/builds")
assert html =~ "Builds"
assert html =~ project.name
snap(html)
end
end
end
```
### Using `render()`
```elixir
defmodule MyApp.LiveIndexTest do
use MyApp.ConnCase, async: true
import Phoenix.LiveViewTest
import ExSnappy
describe "my tests" do
test "List projects and project builds", %{conn: conn, project: project} do
# Visit top level builds page
{:ok, index_live, html} = live(conn, ~p"/app/builds")
assert html =~ "Builds"
assert html =~ project.name
snap(render(index_live))
end
end
end
```
### Using `render_component()`
```elixir
defmodule MyApp.PaginationGeneratorTest do
use ExUnit.Case, async: true
import Phoenix.LiveViewTest
import ExSnappy
test "Lists elements when no next or previous" do
meta = %Flop.Meta{
current_page: 1,
total_pages: 10
}
rendered = render_component(&MyApp.Pagination.paginate/1, meta: meta, base_url: "/hello")
snap(rendered)
assert Floki.find(rendered, "a") |> length() == 10
end
end
```
# Taking multiple screenshots in a single test
Simply provide an explicit name for all but the first call to `snap`.
```elixir
test "multiple screenshots" do
{:ok, index_live, html} = live(conn, ~p"/app/projects")
snap(html)
assert index_live |> element("a", "New Project") |> render_click() =~
"New Project"
render_async(index_live)
snap("after patch", render(index_live))
assert index_live
|> form("#project-form", project: @invalid_attrs)
|> render_change() =~ "can't be blank"
snap("With errors", render(index_live))
assert index_live
|> form("#project-form",
project:
@create_attrs
|> Map.put(:repository_full_name, "some/repo")
)
|> render_submit()
snap("With errors fixed", render(index_live))
end
```