mirror of
https://github.com/Screenly/Anthias.git
synced 2026-06-10 00:57:38 -04:00
* feat(tests): marketing screenshot testbed via integration suite Unifies marketing-asset generation with the existing Playwright integration tests rather than forking a parallel suite. Setting MARKETING_SCREENSHOTS=1 turns the new ``marketing_screenshot`` fixture in tests/conftest.py from a no-op into a capture step that emits @1x/@2x/@3x sibling PNGs under test-artifacts/marketing/ — matching the website's existing overview*.png convention. Seed data moves to tests/_seed_data.py so test_app.py and test_migrate_to_screenly.py render the same Office Space parody content (Initech / TPS / Lumbergh / Chotchkie's / Milton). Two new integration tests — test_home_renders_with_full_schedule and test_add_asset_modal_layers_over_full_schedule — exercise a populated asset table that the per-row smoke tests miss, and double as the ``home`` and ``add-asset`` marketing captures. The new .github/workflows/marketing-screenshots.yaml is workflow_dispatch only, re-runs the integration suite with the env var set, and uploads test-artifacts/marketing/ as a 90-day artifact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: ruff format pass on tests/conftest.py Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(tests): address Copilot review on marketing-screenshot PR - _seed_data.py: clarify that the module-level seed singletons capture their schedule window at import time (only home_seed_assets() is a factory); fix a stale comment that referenced the old WIZARD_LOCAL_VIDEO_BASENAME name. - conftest.py + workflow: docstrings + section comment now describe the actual artefact naming (<name>.png + <name>@2x.png + <name>@3x.png) instead of an inaccurate "@1x/@2x/@3x" shorthand. Matches the existing website/assets/images/overview*.png convention, which is what Hugo's image-set picker resolves. - test_app.py: the two new tests now assert concrete layout properties (each row's name cell has nonzero width; the add-asset modal card has a real bounding box inside the viewport) so a layout regression fails CI without requiring MARKETING_SCREENSHOTS=1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(tests): address Copilot review round 2 on PR #2895 - _seed_data.py: home_seed_assets() now wraps the module-level singletons through a new _with_fresh_window() helper so every dict in the returned list carries a window computed at call time. Honours the "fresh schedule on each call" contract the docstring promises; a time-travelling test no longer gets the import-time freeze for the first / second / last rows. - test_app.py: test_home_renders_with_full_schedule now asserts each row's right edge stays inside the viewport AND that the rightmost action button (Delete) is visible + clickable — a layout regression that pushes the action cluster off-screen now fails CI instead of being caught only by the marketing capture. - test_app.py: test_add_asset_modal_layers_over_full_schedule now calls document.elementFromPoint() at the modal centre and asserts the topmost element lives inside .modal-card. A z-index regression that leaves an asset row floating above the modal is now caught at the assertion layer instead of relying on a visual diff. - marketing-screenshots.yaml: the pytest step is wrapped in nick-fields/retry@v4 (timeout 8 min, 3 attempts) to mirror the existing test-runner.yml integration step. A transient Playwright / SQLite WAL flake no longer aborts the manual artifact pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(tests): address Copilot review round 3 on PR #2895 - _seed_data.py: docstring no longer claims the parody avoids registered/copyrighted material — every name (Initech, Lumbergh, Chotchkie's, Milton, Peter Gibbons) is an Office Space reference. Now describes the content honestly as parody/fair-use of character and company names; the file remains the single swap point for downstream uses that need fully generic naming. - test_migrate_to_screenly.py: the failure-flow test no longer hardcodes abc1.mp4. Both the mocked backend error and the assertion reuse WIZARD_VIDEO_BASENAME, so a future seed rename only has to touch one place. The wizard test's docstring also points at the constant instead of the stale literal. - test_app.py (home): viewport-bound assertion on the Delete button now uses the same +1px tolerance as the row-edge check — Playwright reports floating-point CSS pixels and the strict comparison flaked intermittently under the 3× marketing device scale. - test_app.py (modal): wait for Element.getAnimations({subtree:true}) to settle before asserting the modal card's bbox. The .modal-card has a 220ms modal-in keyframe animation; without explicitly waiting, the marketing screenshot can land mid-fade. Same +1px tolerance applied to the modal viewport-bounds check. - marketing-screenshots.yaml: explicit `permissions: contents: read` block so a workflow_dispatch run against an arbitrary ref cannot inherit broader repo write scopes. The retry command now clears test-artifacts/marketing before each attempt so an earlier partial-success can't stamp stale screenshots onto the artifact upload if a later attempt aborts before the capture tests run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(tests): address Copilot review round 4 on PR #2895 - conftest.py: new session-scoped autouse _reset_marketing_dir fixture clears test-artifacts/marketing/ from inside the container when MARKETING_SCREENSHOTS=1. Replaces the workflow's host-side rm -rf, which failed with permission-denied on a retry because the bind-mounted artefact dir is owned by the root-running anthias-test container. Verified locally: a STALE.png planted before the run is removed before any test executes, then all 9 fresh captures land. - test_app.py (home): adds an explicit .drag-handle visibility + width check on each enabled row. The "drag handle stays reachable" promise in the docstring is now actually asserted, not just implied by the row/Delete-button checks. - test_migrate_to_screenly.py: the mocked migration failure on asset a2 (the URL-backed webpage seed) now uses a URL-fetch error string referencing WIZARD_WEBPAGE_URL — the previous "File not found on device" wording with WIZARD_VIDEO_BASENAME was internally inconsistent (would only make sense for a1/a3, the local-file seeds) and could hide a row-association mistake. - marketing-screenshots.yaml: drops the host-side rm -rf test-artifacts/marketing (now handled inside the container by the fixture above), updates the comment to point at the fixture. PR body also updated to describe the artifact set precisely (<name>.png + <name>@2x.png + <name>@3x.png — no @1x suffix on the base image; matches the existing website overview*.png convention). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
204 lines
7.0 KiB
Python
204 lines
7.0 KiB
Python
"""Office Space-themed sample assets shared by the integration suite.
|
|
|
|
A single source of truth so test_app.py, test_migrate_to_screenly.py
|
|
and any future Playwright test render the same brand-consistent
|
|
content. When ``MARKETING_SCREENSHOTS=1`` is set the same data lights
|
|
up high-DPI captures via the ``marketing_screenshot`` fixture in
|
|
``tests/conftest.py``; in the default integration run the seeds are
|
|
just the data the tests work against, no different from the bare
|
|
dicts they replaced.
|
|
|
|
Sample content is a parody nod to the 1999 film "Office Space" —
|
|
Initech, Lumbergh, Chotchkie's, Milton, Peter Gibbons. No movie
|
|
stills, logos or assets are bundled; URIs point at ``example.com``
|
|
and made-up local paths. The parody is recognisable on purpose so
|
|
the marketing captures read as "real digital signage" rather than
|
|
generic placeholder text, and falls within standard parody/fair-use
|
|
treatment for character and company names. If a downstream use
|
|
requires fully generic naming (e.g. for paid ads), the constants
|
|
below are the single place to swap names. ``asset_id`` values match
|
|
the originals so tests that key off them (e.g. the migration
|
|
wizard's per-asset call-log assertion) keep working unchanged.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from typing import Any
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
def _now_window() -> dict[str, Any]:
|
|
"""Yesterday → tomorrow so every seed renders on the home page.
|
|
|
|
Module-level seed constants below call ``_seed()`` (and therefore
|
|
``_now_window()``) at import time, so their ``start_date`` /
|
|
``end_date`` are captured against wall-clock at the moment this
|
|
module first loads. ``home_seed_assets()`` is a function and
|
|
re-evaluates the window on each call. No current test combines
|
|
these singletons with ``time_machine``; a test that needs to
|
|
travel time should construct fresh seeds via ``_seed()`` or use
|
|
the factory rather than the singletons."""
|
|
return {
|
|
'start_date': timezone.now() - timedelta(days=1),
|
|
'end_date': timezone.now() + timedelta(days=1),
|
|
}
|
|
|
|
|
|
def _seed(
|
|
*,
|
|
asset_id: str,
|
|
name: str,
|
|
mimetype: str,
|
|
uri: str,
|
|
duration: int = 8,
|
|
is_enabled: int = 1,
|
|
play_order: int = 0,
|
|
) -> dict[str, Any]:
|
|
return {
|
|
**_now_window(),
|
|
'asset_id': asset_id,
|
|
'name': name,
|
|
'mimetype': mimetype,
|
|
'uri': uri,
|
|
'duration': duration,
|
|
'is_enabled': is_enabled,
|
|
'nocache': 0,
|
|
'play_order': play_order,
|
|
'skip_asset_check': 0,
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Home-page singletons
|
|
#
|
|
# ``asset_id`` values are preserved verbatim from the pre-rename dicts
|
|
# in test_app.py so any per-id assertion keeps matching. Mimetype short
|
|
# codes ('image' / 'web') likewise match the originals — the URL-form
|
|
# handler stores 'webpage', but several smoke tests fixture rows in
|
|
# directly with 'web', and changing that would shift behaviour beyond
|
|
# the rename.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
INITECH_ANNOUNCEMENT = _seed(
|
|
asset_id='7e978f8c1204a6f70770a1eb54a76e9b',
|
|
name='Initech — Q4 All-Hands Recap',
|
|
mimetype='image',
|
|
uri='https://example.com/initech/q4-allhands-recap.png',
|
|
duration=6,
|
|
play_order=0,
|
|
)
|
|
|
|
LUMBERGH_MEMO = _seed(
|
|
asset_id='4c8dbce552edb5812d3a866cfe5f159d',
|
|
name='Memo: TPS Cover Sheet — Effective Immediately',
|
|
mimetype='web',
|
|
uri='https://example.com/initech/tps-coversheet-memo',
|
|
duration=10,
|
|
play_order=1,
|
|
)
|
|
|
|
CHOTCHKIES_FLAIR_POLICY = _seed(
|
|
asset_id='aa11bb22cc33dd44ee55ff6677889900',
|
|
name="Chotchkie's — Pieces of Flair Policy",
|
|
mimetype='web',
|
|
uri='https://example.com/chotchkies/flair-policy',
|
|
duration=5,
|
|
is_enabled=0,
|
|
play_order=99,
|
|
)
|
|
|
|
|
|
def _with_fresh_window(seed: dict[str, Any]) -> dict[str, Any]:
|
|
"""Return a copy of ``seed`` with ``start_date`` / ``end_date``
|
|
recomputed against current wall-clock. The module-level seed
|
|
singletons freeze their window at import time; this helper lets
|
|
factories that promise "fresh schedule" honour that contract
|
|
without redefining every field."""
|
|
return {**seed, **_now_window()}
|
|
|
|
|
|
def home_seed_assets() -> list[dict[str, Any]]:
|
|
"""A representative 6-asset schedule for marketing-quality home
|
|
captures and the ``test_home_renders_with_full_schedule`` layout-
|
|
regression test. Mix of mimetypes, durations and an explicit
|
|
disabled row so the table renders every visual branch in one go.
|
|
|
|
Every dict in the returned list has its schedule window computed
|
|
on each call — the singletons are wrapped through
|
|
``_with_fresh_window`` so a time-travelling test sees an
|
|
in-window schedule rather than the values captured at import."""
|
|
return [
|
|
_with_fresh_window(INITECH_ANNOUNCEMENT),
|
|
_with_fresh_window(LUMBERGH_MEMO),
|
|
_seed(
|
|
asset_id='b1d31a8f2e7c4d5a9f6b2e1c8d3f0a4e',
|
|
name='Milton — Stapler Inventory Audit',
|
|
mimetype='image',
|
|
uri='/data/anthias_assets/stapler-audit.png',
|
|
duration=5,
|
|
play_order=2,
|
|
),
|
|
_seed(
|
|
asset_id='c2e42b9f3d8e5a6b0c7d3f2a9b4e1d5f',
|
|
name='Office Olympics — Sign-up Open',
|
|
mimetype='web',
|
|
uri='https://example.com/initech/olympics',
|
|
duration=12,
|
|
play_order=3,
|
|
),
|
|
_seed(
|
|
asset_id='d3f53cad4e9f6b7c1d8e4a3b0c5f2e6a',
|
|
name="Chotchkie's — Tuesday Lunch Special",
|
|
mimetype='image',
|
|
uri='/data/anthias_assets/chotchkies-tuesday.png',
|
|
duration=7,
|
|
play_order=4,
|
|
),
|
|
_with_fresh_window(CHOTCHKIES_FLAIR_POLICY),
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Migration-wizard seeds (image + video + URL-backed webpage)
|
|
#
|
|
# Identifiers a1/a2/a3 are referenced directly by the wizard test's
|
|
# call_log assertions ('a1': 1, 'a2': 2, 'a3': 1 etc.), so they stay
|
|
# short. ``WIZARD_VIDEO_BASENAME`` / ``WIZARD_IMAGE_BASENAME`` /
|
|
# ``WIZARD_WEBPAGE_URL`` are exported as named constants so the
|
|
# wizard test's "displayUri strips /data/anthias_assets/ prefix"
|
|
# assertions don't have to hardcode the literal again.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
WIZARD_VIDEO_BASENAME = 'initech-allhands-q4.mp4'
|
|
WIZARD_IMAGE_BASENAME = 'welcome-pgibbons.png'
|
|
WIZARD_WEBPAGE_URL = 'https://example.com/initech/conf-b'
|
|
|
|
|
|
WIZARD_VIDEO = _seed(
|
|
asset_id='a1',
|
|
name='Quarterly All-Hands — Q4',
|
|
mimetype='video',
|
|
uri=f'/data/anthias_assets/{WIZARD_VIDEO_BASENAME}',
|
|
)
|
|
|
|
WIZARD_WEBPAGE = _seed(
|
|
asset_id='a2',
|
|
name='Conference Room B — Today',
|
|
mimetype='webpage',
|
|
uri=WIZARD_WEBPAGE_URL,
|
|
)
|
|
|
|
WIZARD_IMAGE = _seed(
|
|
asset_id='a3',
|
|
name='Welcome — New Hire: Peter Gibbons',
|
|
mimetype='image',
|
|
uri=f'/data/anthias_assets/{WIZARD_IMAGE_BASENAME}',
|
|
)
|
|
|
|
|
|
WIZARD_SEED_ASSETS = [WIZARD_VIDEO, WIZARD_WEBPAGE, WIZARD_IMAGE]
|