Share handed over the raw file address or the item's link: no preview, no time, nothing a person without iPX could do much with. Now, as Overcast does, an episode shares as a short link to its own page, play.ipxreader.app/e/<id>, which anyone can open: the show's art and name, the episode's title, date, length and notes, a player on the publisher's own file starting at ?t= if a time was shared, buttons to subscribe in Apple Podcasts, Overcast, Pocket Casts or Castro, and Open Graph tags for a preview where the link is pasted. ipx-play is a service of its own (crates/play), built like the directory: ipx sends it the episode with its token (POST /v1/shares) and gets the link back. The id comes from the feed's address and the guid, so the same episode is one link whoever shares it. Only episodes of feeds the directory would be sent go there, by the directory's own rule (snapshot_entries), so the two cannot disagree on what is private; ipx-play also refuses an address that looks private. Anything else, and any server without ipx-play set up, shares as before. In the page, Share on an episode opens a menu once the link is back: Share link, Share from 12:34 (the player's clock, or where you left off), Copy link. A menu because the share sheet needs a press of its own, and the request for the link used up the one that opened it. The page runs no script: the start time is the media fragment #t=, and its CSP loads nothing but its stylesheet, the art and the file. Admin, Directory, Sharing episodes sets the address and token. The browser suite starts its own ipx-play; it was not run here (no Caddy on this Mac). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
4.4 KiB
TypeScript
97 lines
4.4 KiB
TypeScript
import { defineConfig } from '@playwright/test';
|
|
import * as setup from './tests/ui/global-setup.ts';
|
|
|
|
// Before anything else, including the servers below.
|
|
setup.prepare();
|
|
|
|
// Measuring the API (#394): an instrumented binary writes its coverage only as it exits, and
|
|
// Playwright otherwise kills its servers outright. Asked to stop, both shut down on their own.
|
|
// Where they write it, as coverage.sh sets it; passed on to each server whatever Playwright passes.
|
|
const PROFILE: Record<string, string> = process.env.LLVM_PROFILE_FILE ? { LLVM_PROFILE_FILE: process.env.LLVM_PROFILE_FILE } : {};
|
|
const STOP = setup.API_COVERAGE ? { gracefulShutdown: { signal: 'SIGTERM' as const, timeout: 15_000 } } : {};
|
|
|
|
// Real browser against a real daemon. The stub-DOM smoke test catches a script that
|
|
// fails to load; it cannot catch a wrong selector, a handler that runs but does nothing,
|
|
// or a page that renders empty -- which is exactly what has slipped through before.
|
|
export default defineConfig({
|
|
testDir: './tests/ui',
|
|
timeout: 30_000,
|
|
expect: { timeout: 10_000 },
|
|
fullyParallel: false, // one daemon, one database
|
|
workers: 1,
|
|
// The page's coverage, with IPX_COVERAGE=1 (#390): cleared first, written last.
|
|
globalSetup: './tests/ui/coverage-setup.ts',
|
|
globalTeardown: './tests/ui/coverage-teardown.ts',
|
|
reporter: process.env.CI ? 'line' : [['list']],
|
|
use: {
|
|
baseURL: 'http://127.0.0.1:8791',
|
|
trace: 'retain-on-failure',
|
|
screenshot: 'only-on-failure',
|
|
// The offline service worker (#389) answers requests before page.route sees them, which the
|
|
// tests that fake an answer rely on. The offline tests allow it for themselves.
|
|
serviceWorkers: 'block',
|
|
},
|
|
webServer: [
|
|
{
|
|
command: 'node tests/ui/fixtures/serve.ts',
|
|
port: 8792,
|
|
reuseExistingServer: false,
|
|
stdout: 'ignore',
|
|
},
|
|
{
|
|
// The directory the Directory shows (#205), on SQLite in the scratch directory.
|
|
command: `${setup.API_COVERAGE ? '' : 'cargo build -q -p ipx-directory && '}exec ${setup.DIRECTORY_BIN} --config ${setup.root}/directory.toml`,
|
|
url: `${setup.DIRECTORY}/healthz`,
|
|
reuseExistingServer: false,
|
|
timeout: 180_000,
|
|
stdout: 'ignore',
|
|
env: { DIRECTORY_DATABASE_URL: `sqlite://${setup.root}/directory.db?mode=rwc`, ...PROFILE },
|
|
...STOP,
|
|
},
|
|
{
|
|
// ipx-play (#447), where a shared episode's link goes, on SQLite in the scratch directory.
|
|
command: `${setup.API_COVERAGE ? '' : 'cargo build -q -p ipx-play && '}exec ${setup.PLAY_BIN} --config ${setup.root}/play.toml`,
|
|
url: `${setup.PLAY}/healthz`,
|
|
reuseExistingServer: false,
|
|
timeout: 180_000,
|
|
stdout: 'ignore',
|
|
env: { PLAY_DATABASE_URL: `sqlite://${setup.root}/play.db?mode=rwc`, ...PROFILE },
|
|
...STOP,
|
|
},
|
|
{
|
|
// The frontend as production serves it (#203): built from web/, served by Caddy on the
|
|
// suite's port, the API passed to the daemon behind it.
|
|
command: `node web/build.mts ${setup.root}/web && exec caddy run --config web/Caddyfile --adapter caddyfile`,
|
|
url: 'http://127.0.0.1:8791/favicon.png',
|
|
reuseExistingServer: false,
|
|
timeout: 60_000,
|
|
stdout: 'ignore',
|
|
env: { IPX_COVERAGE: process.env.IPX_COVERAGE ?? '', WEB_BIND: '127.0.0.1:8791', WEB_ROOT: `${setup.root}/web`, IPX_UPSTREAM: '127.0.0.1:8796', HOME: setup.root, XDG_DATA_HOME: setup.root, XDG_CONFIG_HOME: setup.root },
|
|
},
|
|
{
|
|
// Build first so the tests always run against current source, unless the instrumented
|
|
// build is what is measured (#394).
|
|
command: `${setup.API_COVERAGE ? '' : 'cargo build -q && '}exec ${setup.IPX_BIN} daemon`,
|
|
port: 8796,
|
|
reuseExistingServer: false,
|
|
timeout: 180_000,
|
|
stdout: 'pipe',
|
|
env: {
|
|
IPX_CONFIG: `${setup.root}/config/config.toml`,
|
|
IPX_DATA_DIR: `${setup.root}/data`,
|
|
IPX_LOG: 'ipx=info',
|
|
// Blank, whatever the shell running the suite has: the test daemon sends no traces to
|
|
// production's Tempo and never opens production's database.
|
|
OTEL_EXPORTER_OTLP_ENDPOINT: '',
|
|
IPX_DATABASE_URL: '',
|
|
// The server's TypeSafe key is the shell's: /src/.envrc's is a development key, for this.
|
|
// Without one, the tests of the server paying for Signals see that it cannot.
|
|
TYPESAFE_KEY: process.env.TYPESAFE_KEY ?? '',
|
|
IPX_TYPESAFE_URL: 'http://127.0.0.1:8792/jev',
|
|
...PROFILE,
|
|
},
|
|
...STOP,
|
|
},
|
|
],
|
|
});
|