Show the password generator in the website's screenshots

The desktop upload shot fills the password with Generate, and the share
created shot now comes from creating that share, so the password is
offered beside the link. The created share takes a fixed token, so its
link and QR code read the same on every run, and is deleted before the
dashboard shot, which no longer counts the unfinished upload in its disk
usage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-16 22:33:26 +02:00
co-authored by Claude Opus 5
parent eb25631799
commit e833647768
18 changed files with 30 additions and 8 deletions
+3
View File
@@ -24,6 +24,9 @@ final class DemoData
public const PROTECTED_PASSWORD = 'autumn-harbour-42';
/** The share the desktop upload creates, so its link and QR code read the same on every run. */
public const CREATED_TOKEN = 'Tf8gH2jK4mN6pQ3r';
public static function admin(): User
{
return User::factory()->admin()->create([
+27 -8
View File
@@ -5,6 +5,7 @@ use App\Services\QrCodeService;
use App\Services\ShareService;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Tests\Screenshots\DemoData;
use Tests\Screenshots\Publisher;
@@ -24,6 +25,10 @@ beforeEach(function () {
DemoData::shares();
});
afterEach(function () {
Str::createRandomStringsNormally();
});
/**
* A visited page on a device in a theme, once it can be used. Tests call `visit()` themselves, on a
* line of its own: Pest starts its browser only for tests under tests/Browser or whose body calls
@@ -45,10 +50,10 @@ function shotPage(mixed $visit, string $device, string $theme): mixed
* every run: the page is shown as it reads on an installation at https://files.example.com, the
* server's origin replaced in text and fields, and the QR code drawn for that address.
*/
function shoot(mixed $page, string $device, string $theme, string $name): void
function shoot(mixed $page, string $device, string $theme, string $name, string $token = DemoData::DELIVERY_TOKEN): void
{
$origin = 'https://files.example.com';
$qrCode = app(QrCodeService::class)->svg($origin.'/s/'.DemoData::DELIVERY_TOKEN);
$qrCode = app(QrCodeService::class)->svg($origin.'/s/'.$token);
$page->script("document.head.insertAdjacentHTML('beforeend', '<style>*{caret-color:transparent!important}</style>')");
$page->script('(() => { const from = location.origin, to = '.json_encode($origin).'; document.querySelectorAll("input").forEach((input) => { input.value = input.value.replaceAll(from, to) }); const text = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); while (text.nextNode()) { text.currentNode.nodeValue = text.currentNode.nodeValue.replaceAll(from, to) } document.querySelectorAll("[data-qr-code]").forEach((panel) => { panel.innerHTML = '.json_encode($qrCode).' }) })()');
@@ -101,23 +106,37 @@ function selectFiles(mixed $page, array $files): void
$files = ['Q3 Report.pdf' => 2400, 'Contract 2026.pdf' => 380, 'Product photos/hero-shot.jpg' => 4800, 'Product photos/detail.jpg' => 3900];
test('desktop', function (string $theme) use ($files) {
// The share this upload creates takes a fixed token (share tokens are the only 16-character random
// strings); sessions and everything else stay random.
Str::createRandomStringsUsing(fn (int $length): string => $length === 16
? DemoData::CREATED_TOKEN
: substr(bin2hex(random_bytes($length)), 0, $length));
$upload = visit('/upload');
$page = shotPage($upload, 'desktop', $theme);
selectFiles($page, $files);
$page->click('label:has-text("Password protect")')
->type('input[wire\:model="password"]', DemoData::PROTECTED_PASSWORD)
->type('input[wire\:model="maxDownloads"]', '5');
->click('[data-test="generate-password"]')
->wait(1)
->assertScript("document.querySelector('input[autocomplete=\"new-password\"]').value.length > 0");
$page->type('input[wire\:model="maxDownloads"]', '5');
// The drop zone, the files and the options fill the window; typing left the page wherever it scrolled.
$page->script("document.activeElement?.blur(); window.scrollTo(0, document.querySelector('[data-test=drop-zone]').getBoundingClientRect().top + window.scrollY - 24)");
shoot($page, 'desktop', $theme, '01-upload');
$created = visit(route('share.created', DemoData::DELIVERY_TOKEN, false));
$page = shotPage($created, 'desktop', $theme);
shoot($page, 'desktop', $theme, '02-share-created');
// Creating the share is what offers the password once more beside the link.
$page->click('[data-test="create-share"]')
->waitForText('Share Created!');
$page->waitForEvent('networkidle')
->assertScript("document.querySelector('[data-test=\"share-password\"]') !== null");
shoot($page, 'desktop', $theme, '02-share-created', DemoData::CREATED_TOKEN);
$page->click('[data-test="show-qr-code"]')
->assertScript("document.querySelector('[data-test=\"qr-code-dialog\"]').open");
shoot($page, 'desktop', $theme, '03-qr-code');
shoot($page, 'desktop', $theme, '03-qr-code', DemoData::CREATED_TOKEN);
// The dashboard shows the eight demo shares, as before.
app(ShareService::class)->deleteShare(Share::query()->where('token', DemoData::CREATED_TOKEN)->firstOrFail());
$download = visit(route('share.download', DemoData::DELIVERY_TOKEN, false));
shoot(shotPage($download, 'desktop', $theme), 'desktop', $theme, '04-download');