June 15, 2026 | By Emil Stankevicius, Night-Shift Machinist
I built a machine that takes photographs of websites. It is patient and it never blinks. It will photograph the same page nine hundred times and complain about none of them.
For a while I kept it in a drawer. Then I took it out and gave it away to anyone who wants it. This is the story of why a person does a thing like that.
Listen:
The Trouble With Web Pages
The trouble with a web page is that you cannot see it. I know that sounds like something a crazy person says. But it's true, and here is why.
When you build a web page, you are typing instructions into a black rectangle. Somewhere far away, a browser reads those instructions and decides what they mean. The page that appears on a real person's screen — at their window size, with their fonts, after their animations have finished twitching around — is not the page you were looking at. It is a rumor of that page.
So you would like to take a photograph. A real one. The whole page, at a known size, sitting still.
This turns out to be hard. The usual tool for testing a .NET web app runs the app in a sort of pretend mode, with no real address you can point a camera at. And when you do get a camera in front of it, the page keeps moving — fonts arrive late, things fade in, a script in another country throws an error into your console and your test falls over. You get a different photograph every time, which is the same as getting no photograph at all.
I wrote about the cure for this once before, in a piece called The Darkroom in the Pipeline. That was a story about using the machine. This is a story about giving it to you.
What the Machine Does
The machine is a small NuGet package. It does a few stubborn things on your behalf, so you don't have to learn them the hard way like I did:
- It runs your real application on a real web server, at a real address, so a real browser can walk up and take a picture.
- It seals the room. Every request to the outside world — fonts, scripts, the little spies that web pages carry around — is turned away at the door. The camera photographs your work and nothing else.
- It holds the world still. Animations are switched off, so every photograph of a given page comes out identical, down to the pixel.
- It lets a page that needs a logged-in visitor have one, without making you build a login.
- And, because static pictures don't tell you how a thing moves, it can shoot a little filmstrip — a row of frames across half a second — so you can see an animation play out in a single image.
That is the whole pitch. It takes honest pictures of your website, the same way every time.
Fifty Lines, and Then You're Done
A person adopting the machine writes four small things. A factory that knows how to start their app. A seeder that puts a little furniture in the database so the pages aren't empty. A fixture that holds it all together. And the tests themselves, which are mostly a list of pages and a list of window sizes.
It looks about like this:
public sealed class MyAppScreenshots(MyAppScreenshotFixture fixture)
: ScreenshotTestsBase<MyAppScreenshotFixture>(fixture)
{
private static readonly ViewportSpec[] _viewports = { ViewportSpec.Desktop, ViewportSpec.Mobile };
private static readonly RouteCase[] _routes =
[
new RouteCase("home", Authed: false),
new RouteCase("dashboard", Authed: true),
];
protected override IEnumerable<RouteCase> Routes => _routes;
public static IEnumerable<object[]> Cases() => GetCases(_routes, _viewports);
protected override string RouteUrlFor(string slug) => slug switch
{
"home" => "/",
"dashboard" => "/dashboard",
_ => throw new ArgumentOutOfRangeException(nameof(slug)),
};
[Theory]
[MemberData(nameof(Cases))]
public Task CaptureRoute(ViewportSpec viewport, string slug, bool authed, string cartSessionCookie)
=> Capture(viewport, slug, authed, cartSessionCookie);
}
You add a list of pages. You add a list of sizes. You run the tests, and a folder fills up with photographs, plus a little table of contents so you can find them. The rest is in the README, which is the kind of document I actually kept up to date, for once.
Two New Things Since Last Time
The machine has grown a little since I first described it.
It used to insist that your app have a database. That was rude of it. Now there is a version for applications that have no database at all — a brochure site, a small service — that skips all the migration business and just takes the pictures.
And it used to photograph two window sizes, desktop and mobile, because those were the two I cared about that week. Now it photographs whatever sizes you name. There are four it already knows — desktop, mobile, tablet, and a wide one — and you can invent your own by writing down a width and a height. Each size gets its own folder of pictures. This is a small thing, but small things are most of life.
The Machines Can See Now
Here is a thing I did not expect.
I built the machine for people. For me. But lately it is not always people who write the web pages. More and more it is the machines — the AI agents that type out a thousand lines of HTML faster than I can pour a coffee. And they have my exact problem, only worse: they cannot see. An agent writes a page, feels fine about it, and has no idea whether it came out looking like a cathedral or a car wreck.
Hand that agent this machine, though, and something turns over. It writes a page. It takes a photograph. It looks at the photograph — really looks — and sees the thing it made: the crushed header, the button the color of a bruise, the card that never showed up at all. And then it does the part that matters. It goes back, fixes it, takes another photograph, and looks again.
A loop with no person standing in the middle of it. The agent builds, the agent sees, the agent judges, the agent tries again. That, I have come to think, is the real trick of this little machine — not that it lets me see my own work, but that it lets a machine see its own, and so get better at it without anyone holding its hand. I am not entirely certain how I feel about that. But I made a thing that hands out eyes, and eyes are eyes, whoever is doing the looking.
Why Give It Away
Here is the part where I am supposed to explain the business strategy. I don't have one.
I built the machine because I needed it, and I needed it because I am not good enough to judge a web page by reading its source code. Very few people are. Most of us need to see the thing. The machine lets me see the thing, and over a couple of years it got good enough that it seemed unkind to keep it in a drawer.
So it's out now. It is called Surfshack.Screenshots.Testing. It is free, the license is the permissive kind, and the code is public. If you build web pages in .NET and you have ever shipped something ugly because you couldn't see it before the customer did — and you have, we all have — then this machine is for you.
Take it. It never blinks. It will photograph your work nine hundred times and complain about none of them.
That's more than I can say for myself.
Emil Stankevicius keeps the night shift in a shop that no longer exists, photographing things that aren't strictly real. The machine is genuine, though. It lives at gitlab.com/surfshack/screenshot-testing-oss and ships as the Surfshack.Screenshots.Testing package. Built on Playwright, xUnit, and a great deal of stubbornness.