DataTalksClub / DataTalksClub/website
Keep Spotify creator podcast embeds offline in Playwright
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Product outcome
Make every local Playwright podcast scenario fully offline while preserving the approved Spotify for Creators iframe contract. The exact approved embed is fulfilled by the test harness with deterministic local bytes in the shared page/context fixtures and in every direct browser context that can render a podcast detail, including JavaScript-disabled accessibility scenarios. Every other external request remains denied and test-failing.
This is test infrastructure only. It does not change the podcast page, player/fallback behavior, provider integration, CSP, content projection, or production network policy.
Authority and exact evidence
_docs/PROCESS.md: versioned verification, independent tester, PM, commit/push, and on-call gates._docs/specs/07-security-privacy-operations.md: fail-closed provider/network behavior and non-disclosing evidence._docs/specs/10-verification-strategy.md: deterministic offline browser tests, browser-harness evidence, and full release gates.- #216 owns podcast player/product behavior. #270 owns localized Show Notes resource validation. #232 exposed this harness defect but does not own it.
Exact diagnosis on face8e4808d65afbf0374d1ced7a88079950d663:
- the shared context fulfills validated YouTube embeds but does not fulfill Spotify creator embeds;
- several suites create
browser.new_context(...)directly, bypassing the shared route/teardown guard; - the accessibility assertion passed, then teardown reported the exact iframe navigation
https://creators.spotify.com/pod/profile/datatalksclub/embed/episodes/How-to-Build-and-Evaluate-AI-systems-in-the-Age-of-LLMs---Hugo-Bowne-Anderson-e39vt24
asnet::ERR_BLOCKED_BY_CLIENT; - CSP already permits the production provider and is not the defect.
The retained trace digest is c6e2c3d188a4d169eddadb628994ad61ac1b881d8d7b6ff8da905923c7633cb3. It is diagnostic only, not reusable acceptance evidence.
Exact offline route contract
One canonical policy, no duplicated provider stubs
Create one Playwright-harness module that owns:
- pure request classification;
- deterministic provider response constants;
- installation of the same catch-all route policy on shared and direct contexts; and
- bounded denied-request reporting.
conftest.py calls this policy from the regular shared context route and from any separately maintained strict context. Direct local contexts use one context-manager/factory from the same module rather than calling browser.new_context(...) plus ad hoc page.route(...) stubs.
Remove the duplicated _stub_video_provider helpers from the podcast suites. Do not copy Spotify matching/response logic into individual tests. A static/AST census must fail if an in-scope local podcast/accessibility context bypasses the central factory or reintroduces a provider-specific page route.
Requests that may continue
The local harness continues only:
about:,blob:, anddata:URLs;- the exact active
live_serverorigin, including its assigned port; - already registered, provenance-validated offline fixture URLs;
- the existing exact
https://www.youtube-nocookie.com/embed/...test fulfillment; and - the Spotify creator fulfillment below.
Safety-authorized remote markers retain their existing explicit authorization behavior and must not receive a synthetic provider response unless their current contract already selects local/offline mode.
Exact Spotify creator match
Fulfill only a child-frame GET document request with:
- scheme
https; - exact authority
creators.spotify.comwith no credentials or explicit port; - no query and no fragment;
- path
/pod/<kind>/<show>/embed/episodes/<episode>; <kind>exactlyprofileorshow; and<show>and<episode>each matching the existing product segment grammar^[A-Za-z0-9][A-Za-z0-9_-]{0,255}$.
The known escaped request above must match. Do not use a blanket creators.spotify.com/** rule.
Near matches remain denied: HTTP, www, suffix/prefix/lookalike hosts, credentials, any port, protocol-relative form, query/fragment, parent/top-level navigation, fetch/XHR/script/image requests, non-GET methods, missing/extra/empty path segments, wrong kind/order, percent-encoded separator/traversal/control forms, malformed percent escapes, or overlong/invalid segment characters.
open.spotify.com, Spotify platform links, creator profile/non-embed pages, and provider APIs are not fulfilled by this issue.
Exact deterministic response
The matching request is satisfied entirely by route.fulfill; no DNS, socket, HTTP, redirect, service worker, cache reuse, or provider call occurs.
- status:
200; - body, UTF-8 exactly:
<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Offline Spotify embed</title></head><body></body></html>; - headers:
Content-Type: text/html; charset=utf-8;Cache-Control: no-store;X-Content-Type-Options: nosniff.
The body contains no script, style, image, link, form, storage access, or subresource and cannot make a second request. The existing YouTube fulfillment remains semantically unchanged.
Deny and evidence behavior
Any URL outside the allow/fulfill classes is aborted locally as blockedbyclient and causes the owning local test to fail at its context/fixture boundary. Diagnostics contain only a bounded hostname plus path and reason category; they never include query, fragment, credentials, headers, cookies, tokens, or response body. Expected navigation-cancellation handling remains scoped to same-origin lifecycle events and cannot suppress an external denial.
Direct-context migration boundary
Migrate every current direct context that can render a podcast detail or participates in the generic public no-JavaScript matrix to the central offline context manager/factory. At minimum this includes the direct-context sites in:
playwright_tests/test_podcast_design_parity.py;playwright_tests/test_podcast_episode_parity.py;playwright_tests/test_podcast_episode_graph.py;playwright_tests/test_podcast_seasons.py; andplaywright_tests/test_accessibility.py::test_javascript_off_public_reads_remain_semantic.
Any #270 direct/no-JavaScript affected-detail context in the repaired-base integration must use the same factory. Other direct local contexts may be migrated mechanically when required to make the factory boundary uniform, but their assertions, viewport/options, screenshots, and product behavior must not change.
Allowed files
conftest.py;- one new central helper below
playwright_tests/; playwright_tests/test_network_guard.pyand one focused browser-harness test module if needed;- only the direct-context consumer modules listed above, plus a #270-owned podcast browser file if that exact repaired-base context already exists.
No application/runtime/template/static/spec/projection/generated-content/CSP/workflow/dependency file is allowed. If production code or a broader provider allowlist appears necessary, stop and return to PM.
Dependencies and ordering
#279 has no product dependency and is engineer-ready from the repaired current-main base. It is a test-infrastructure prerequisite for terminal browser acceptance of the ordered #223 → #270 candidate because those podcast routes must no longer call or abort against Spotify during local verification.
Land accepted #279 before constructing the final #223/#270 integration fingerprint. #216 remains product authority, not an open implementation dependency. #232 remains separate. #270 must rebase its browser contexts onto the central factory rather than restore a local provider stub.
Non-goals
- No product iframe URL, player availability/fallback, title, layout, accessibility, JavaScript, content, projection, source, route, canonical, cache, or provider-link change.
- No CSP/header/template edit, CSP bypass expansion, provider cookie/storage emulation, Spotify SDK/API behavior, media playback, redirect, analytics, tracking, or service-worker behavior.
- No live provider smoke, provider account/secret, DNS/network request, recorded provider response, HAR replay, browser cache dependency, or proxy.
- No blanket Spotify/domain allowlist and no suppression/ignore of failed requests, console errors, teardown failures, or
ERR_BLOCKED_BY_CLIENT. - No weakening of the network guard, navigation-cancellation tracker, external-denial evidence, flake policy, timeout, retry, quarantine, or screenshot rules.
- No #216 product repair, #270 resource-validator change, #232 homepage change, or unrelated direct-context refactor beyond mechanical adoption of the central factory.
Acceptance criteria
Policy and unit contracts
- One canonical helper/classifier supplies the shared context and every in-scope direct/no-JavaScript context; duplicated page-level YouTube/Spotify stubs are removed.
- The exact known Spotify creator iframe request matches and receives the exact 200/body/three-header response without any external network operation.
- Both
profileandshowvalid embed shapes pass; the complete near-match matrix above is denied without invokingroute.fulfill. - Existing YouTube offline fulfillment, registered offline fixtures, exact live-server access, authorized remote behavior, same-origin cancellation tracking, teardown diagnostics, and artifact redaction remain unchanged.
- Every unrelated external request is aborted and makes the local test fail with a bounded host/path category; query/fragment/credential/header/cookie/body values never appear in evidence.
- Static/AST coverage proves every in-scope direct context uses the central factory and no podcast suite retains a provider-specific
page.routehelper.
Browser contracts
- A shared-fixture podcast-detail scenario loads the outer YouTube player and Spotify creator iframe entirely offline, records the Spotify response as 200 with exact headers/body, and finishes with no request/console/page/teardown failure.
- Direct-context JavaScript-enabled and JavaScript-disabled podcast-detail scenarios use the same route policy and finish without provider traffic or teardown failure; no-JavaScript still shows the existing native fallback/content state.
- The generic accessibility no-JavaScript matrix reaches its podcast detail at desktop/mobile and terminates green with no escaped Spotify request.
- A denied near-match/unrelated external request is proven fail-closed in focused harness tests; it cannot be mistaken for navigation cancellation or an expected offline abort.
- Product assertions for iframe title/src, fallback availability, keyboard reachability, 320 px reflow, accessibility, CSP, and #270 affected routes remain unchanged and green.
Verification and lifecycle
- Focused network-guard/helper tests, affected podcast/accessibility browser suites, test-collection policy, and
make test-cipass. - A fresh full versioned verification profile passes quality, Django, full Playwright, graph-selected independent screenshots, container, evidence validation, migration drift, and all selected checks with no required skip, partial result, retry, timeout extension, quarantine, or external request. Because the actual candidate changes browser-harness/direct-context modules, screenshots may not be forced to
not_applicableby an issue-level exception. - Before tester handoff, the engineer posts a Software Engineer Progress Report with the frozen identity and evidence enumerated below. The independent tester then independently recomputes and records the exact base/candidate/diff/helper-policy/selection/plan/graph/render/report/screenshot digests, terminal counts, direct-context census, and zero unexpected external requests. No trace containing credentials or provider payload is published.
- PM accepts the exact frozen harness-only candidate after independent tester PASS.
- [ON-CALL] After the focused
Closes #279commit is merged/pushed, the exact release SHA reaches terminal green CI and deployment observation before #279 is treated as complete.
Engineer report and frozen handoff gate
The staged candidate remains in engineering until its owner posts one ## Software Engineer Progress Report on #279 containing:
- worktree/branch, exact repaired base and HEAD, staged/unstaged state, complete changed-path list, and one reproducible binary diff SHA-256;
- central helper/classifier/response-constant identity and digest, exact direct-context migration census, and proof that no application/template/static/projection/CSP/workflow/dependency file changed;
- focused command/results for classification, deny/redaction, shared context, direct JavaScript/no-JavaScript podcast, accessibility matrix, collection/static census, and
make test-ci; - versioned selection/plan/graph/render digests and every
rerun,reused,skipped, andnot_applicabledisposition; - current broad-gate status and an explicit
screenshots: pending_independent_testerdisposition when the graph selects them; - confirmation that the worktree is frozen, uncommitted, unpushed, and will not change during independent verification.
No tester handoff begins from a verbal/staged-state claim, an unfrozen worktree, a missing diff digest, or a report that calls graph-selected screenshots not_applicable. Any source, graph, policy, plan, or relevant environment change after the report invalidates the handoff and returns to engineering.
Render, migration, and screenshot disposition
- Product render: application/template/static bytes remain unchanged; the deterministic iframe document is harness-only and is not public product output.
- Screenshots: required for the actual harness/direct-context candidate. The versioned ownership graph classifies the changed Playwright harness and consumer modules as screenshot-impacting. Follow the graph-selected independent screenshot component exactly; do not add a planner/ownership exception and do not override it as
not_applicablemerely because production DOM bytes are unchanged. - Minimum independent visual evidence: under
.tmp/screenshots/issue-279/, capture and inspect the trace-backed/podcast/practical-llm-engineering-and-rag.htmlat desktop 1440x900 with JavaScript enabled and mobile 390x844 with JavaScript disabled, plus every additional route/state the recomputed plan selects. Evidence must show the intended podcast page/fallback state, readable content, no debug/error page, no horizontal overflow, and no broken player surface; its manifest records candidate/render/PNG digests and network/console/page-error verdicts. - Attribution: screenshots are regression evidence for the changed browser harness, not evidence of a public product redesign. Existing screenshot-producing suites may satisfy a selected state only when their envelope, route, viewport, candidate, and render fingerprints validate exactly.
- Migrations: no model/migration change; new migration execution is
not_applicable, while migration drift remains required. - JavaScript/no-JavaScript: both are required network-boundary and visual evidence because direct no-JavaScript contexts caused the escape; this does not imply a product behavior change.
Focused scenarios
- Shared
pagefixture: open the exact affected episode; assert iframe request classification, deterministic response, and clean teardown. - Direct context, JavaScript on/off: open an episode with creator audio; assert the same classification/response and existing player/fallback state.
- Generic accessibility matrix: run the podcast-detail state at both maintained viewports with JavaScript off and confirm zero denied/failed provider requests.
- Classifier table: exact known URL, both valid kinds, min/max segments, and every scheme/authority/method/resource/path/query/fragment near miss.
- Deny-all regression: attempt one unrelated external request and prove it is aborted, reported safely, and cannot be swallowed by cancellation tracking.
- Independent render evidence: capture/open the trace-backed podcast detail at 1440 JavaScript-on and 390 JavaScript-off plus every graph-selected state; bind each image to the frozen candidate/render digest and prove the intended page, fallback/player surface, containment, and clean browser evidence.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with conftest.py and the direct-context sites in the listed podcast and accessibility modules, then read playwright_tests/test_network_guard.py and the existing shared route policy. Run the focused network-guard tests first to understand the offline and denial contracts. Done means all in-scope contexts use one central factory, near matches remain denied, and the affected browser suites pass without external requests or teardown failures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, python
- Domain
- devtools, testing-qa
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100