Upload Folder
Test source at commit 5274a75a Β· v0.2.44
UC-02: Folder upload β gallery + browse view (P1).
Test flow:
- Upload a folder containing 3+ images and a markdown file
- Verify gallery mode is auto-selected (3+ images triggers gallery)
- Verify thumbnail grid renders with correct file count
- Click a thumbnail β verify lightbox opens
- Close lightbox β click βFolder viewβ link
- Verify browse view loads with folder tree
- Click a file in the tree β verify it opens in preview
- Click βGallery viewβ β verify it switches back
View source on GitHub β tests/qa/v030/p1__upload__folder/test__upload__folder.py
Test Methods
| Method |
Description |
Screenshots |
upload_zip_and_gallery_view |
Upload a zip with 3+ images; verify gallery mode is shown. |
5 |
gallery_thumbnail_grid |
Create a zip upload via API, open gallery, verify thumbnail grid. |
1 |
browse_view_folder_tree |
Open browse view for the zip; verify folder tree is present. |
1 |
mode_switch_preserves_hash |
Gallery β browse mode switching preserves the hash fragment. |
1 |
Screenshots
01 Upload Page
Upload page loaded

Deterministic view (non-dynamic areas only)

02 File Selected
Zip file selected β delivery step

Deterministic view (non-dynamic areas only)

03 Share Step
Share mode step

Deterministic view (non-dynamic areas only)

04 Upload Done
Upload complete β link shown

Deterministic view (non-dynamic areas only)

05 Download Gallery
Gallery view after zip download

Deterministic view (non-dynamic areas only)

06 Gallery Thumbnails
Gallery thumbnail grid

Deterministic view (non-dynamic areas only)

07 Browse View
Browse view with folder tree

Deterministic view (non-dynamic areas only)

08 Switched To Browse
Switched to browse view

Deterministic view (non-dynamic areas only)

View test source β tests/qa/v030/p1__upload__folder/test__upload__folder.py
```python
"""UC-02: Folder upload β gallery + browse view (P1).
Test flow:
1. Upload a folder containing 3+ images and a markdown file
2. Verify gallery mode is auto-selected (3+ images triggers gallery)
3. Verify thumbnail grid renders with correct file count
4. Click a thumbnail β verify lightbox opens
5. Close lightbox β click "Folder view" link
6. Verify browse view loads with folder tree
7. Click a file in the tree β verify it opens in preview
8. Click "Gallery view" β verify it switches back
v0.3.1 notes:
- Gallery folder naming changed: v0.3.1 uses __gallery__{8-char-hash}
instead of v0.3.0's _gallery.{16-char-hash}. These tests do not inspect
the internal zip structure, so they pass for both naming conventions.
- Gallery view is NOT patched by v0.3.1 (browse-only overlay), so gallery
behaviour is identical to v0.3.0.
- Gallery rename backward-compatibility is tested in tests/qa/v031/p3__gallery_rename/.
"""
import pytest
import zipfile
import io
from playwright.sync_api import expect
from tests.qa.v030.browser_helpers import goto, handle_access_gate
pytestmark = pytest.mark.p1
# Minimal 1Γ1 PNG bytes (valid PNG, tiny file)
_PNG_HEADER = (
b'\x89PNG\r\n\x1a\n'
b'\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01'
b'\x08\x02\x00\x00\x00\x90wS\xde'
b'\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N'
b'\x00\x00\x00\x00IEND\xaeB`\x82'
)
def _make_folder_zip():
"""Build an in-memory zip with 3 PNG images + 1 markdown file."""
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr("my-folder/image1.png", _PNG_HEADER)
zf.writestr("my-folder/image2.png", _PNG_HEADER)
zf.writestr("my-folder/image3.png", _PNG_HEADER)
zf.writestr("my-folder/readme.md", "# Test folder\n\nThis is a test.")
buf.seek(0)
return buf.read()
class TestFolderUpload:
"""Upload a folder zip and verify gallery + browse views."""
def test_upload_zip_and_gallery_view(self, page, ui_url, send_server, screenshots):
"""Upload a zip with 3+ images; verify gallery mode is shown."""
goto(page, f"{ui_url}/en-gb/")
handle_access_gate(page, send_server.access_token)
screenshots.capture(page, "01_upload_page", "Upload page loaded")
# Feed the zip file
zip_bytes = _make_folder_zip()
page.locator("#file-input").set_input_files({
"name" : "my-folder.zip",
"mimeType": "application/zip",
"buffer" : zip_bytes,
})
# Wait for wizard to auto-advance to delivery step
page.locator("#upload-next-btn").wait_for(state="visible")
screenshots.capture(page, "02_file_selected", "Zip file selected β delivery step")
# Delivery β Share mode
page.locator("#upload-next-btn").click()
page.locator("[data-mode]").first.wait_for(state="visible")
screenshots.capture(page, "03_share_step", "Share mode step")
# Select combined link β auto-advances to confirm step
page.locator('[data-mode="combined"]').click()
page.wait_for_timeout(500) # let confirm step transition settle
# Confirm β Encrypt & Upload β done step shows link as
page.locator("#upload-next-btn").click()
page.locator("a[href*='#']").first.wait_for(state="attached", timeout=20_000)
screenshots.capture(page, "04_upload_done", "Upload complete β link shown")
# Extract download URL
download_url = page.locator("a[href*='#']").first.get_attribute("href") or ""
if not download_url:
for el in page.locator("input[readonly]").all():
val = el.get_attribute("value") or ""
if "#" in val:
download_url = val
break
assert download_url, "No download link found after folder upload"
if download_url.startswith("/"):
download_url = f"{ui_url}{download_url}"
# Open the download page β expect gallery view (3 images auto-select gallery)
dl_page = page.context.new_page()
try:
goto(dl_page, download_url)
screenshots.capture(dl_page, "05_download_gallery", "Gallery view after zip download")
page_text = dl_page.text_content("body") or ""
assert any(kw in page_text.lower() for kw in ["gallery", "image", "thumbnail", "photo"]), \
"Gallery view not detected for image-heavy zip"
finally:
dl_page.close()
def test_gallery_thumbnail_grid(self, page, ui_url, transfer_helper, screenshots):
"""Create a zip upload via API, open gallery, verify thumbnail grid."""
zip_bytes = _make_folder_zip()
tid, key_b64 = transfer_helper.upload_encrypted(zip_bytes, "my-folder.zip")
gallery_url = f"{ui_url}/en-gb/gallery/#{tid}/{key_b64}"
goto(page, gallery_url)
# Wait for the body to contain meaningful content
expect(page.locator("body")).not_to_be_empty(timeout=10_000)
screenshots.capture(page, "06_gallery_thumbnails", "Gallery thumbnail grid")
page_text = page.text_content("body") or ""
assert "error" not in page_text.lower() or "gallery" in page_text.lower(), \
"Gallery page shows error instead of content"
def test_browse_view_folder_tree(self, page, ui_url, transfer_helper, screenshots):
"""Open browse view for the zip; verify folder tree is present."""
zip_bytes = _make_folder_zip()
tid, key_b64 = transfer_helper.upload_encrypted(zip_bytes, "my-folder.zip")
browse_url = f"{ui_url}/en-gb/browse/#{tid}/{key_b64}"
goto(page, browse_url)
expect(page.locator("body")).not_to_be_empty(timeout=10_000)
screenshots.capture(page, "07_browse_view", "Browse view with folder tree")
page_text = page.text_content("body") or ""
assert "error" not in page_text.lower() or "browse" in page_text.lower(), \
"Browse page shows error instead of content"
def test_mode_switch_preserves_hash(self, page, ui_url, transfer_helper, screenshots):
"""Gallery β browse mode switching preserves the hash fragment."""
zip_bytes = _make_folder_zip()
tid, key_b64 = transfer_helper.upload_encrypted(zip_bytes, "my-folder.zip")
expected_hash = f"#{tid}/{key_b64}"
# Start at gallery
gallery_url = f"{ui_url}/en-gb/gallery/{expected_hash}"
goto(page, gallery_url)
expect(page.locator("body")).not_to_be_empty(timeout=10_000)
# Look for "Folder view" / "Browse" link and click it
browse_link = page.locator(
"a:has-text('Folder view'), a:has-text('Browse'), a[href*='browse']"
).first
if browse_link.is_visible(timeout=5_000):
browse_link.click()
expect(page.locator("body")).not_to_be_empty(timeout=10_000)
screenshots.capture(page, "08_switched_to_browse", "Switched to browse view")
# Verify navigation reached browse (hash preservation depends on UI version)
current_url = page.url
assert "/browse/" in current_url, \
f"Mode switch did not navigate to browse: {current_url}"
```
</details>