Files
Magent/scripts/review_newsletters_ui.cjs
Assclaw b0f8c89db7
Magent CI/CD / verify (push) Successful in 10m54s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 1m14s
Add Grizzlyflix newsletters with curated editions and weekly delivery
2026-09-09 23:42:52 +12:00

217 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Fixture-only review: every API request is intercepted, including all send actions.
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const { chromium } = require(process.env.REVIEW_PLAYWRIGHT || 'playwright')
const base = process.env.REVIEW_BASE || 'http://localhost:3114'
const output = process.env.REVIEW_DIR
const preview = JSON.parse(fs.readFileSync(process.env.REVIEW_EMAIL_FIXTURE, 'utf8'))
const movie = '1'.repeat(32), series = '2'.repeat(32), editionId = 'e'.repeat(32)
const clone = (value) => JSON.parse(JSON.stringify(value))
const titles = [{ id: movie, title: 'Arrival', type: 'movie', year: 2016, has_artwork: true, items: [{ id: movie }], selected: true, featured: false }, { id: series, title: 'Severance', type: 'series', year: null, has_artwork: true, items: [{ id: '3'.repeat(32), season: 2 }, { id: '4'.repeat(32), season: 2 }], selected: true, featured: false }]
const draft = () => ({ id: editionId, subject: 'Weekend discoveries', intro: '', revision: 1, state: 'draft', origin: 'manual', send_at: null, created_at: Date.now()/1000, content: { titles: clone(titles), total_titles: 2, period_start: '2026-09-04T09:00:00+00:00', period_end: '2026-09-11T09:00:00+00:00' } })
;(async () => {
const browser = await chromium.launch({ headless: true })
try {
const context = await browser.newContext()
await context.addCookies([{ name: 'magent_logged_in', value: '1', url: base }])
const calls = []
let role = 'admin', failSave = false, failPreview = false, email = 'viewer@example.test', tokenState = 'ready'
let heldConfirmation = null
let settings = { enabled: false, weekday: 4, hour: 9, limit_titles: 12, public_url: 'https://beta.example.test', intro: '', revision: 1, next_send_at: null }
let preference = { state: 'off', email, can_subscribe: true, detail: 'Ready', schedule_enabled: false, weekday: 4, hour: 9, next_send_at: null, resend_after: null }
let edition = null, deliveries = []
const overview = () => ({ settings, ready: true, detail: 'Ready', subscribers: 12, editions: edition ? [{ ...edition, titles: edition.content.titles.filter((title) => title.selected).length, content: undefined, period_start: edition.content.period_start, period_end: edition.content.period_end }] : [], deliveries, total: deliveries.length })
await context.route('**/api/**', async (route) => {
const request = route.request(), url = new URL(request.url()), payload = request.postDataJSON()
calls.push({ method: request.method(), path: url.pathname, payload })
if (url.pathname === '/api/auth/me') return route.fulfill({ json: { username: 'Fixture viewer', role, email } })
if (url.pathname === '/api/auth/profile') return route.fulfill({ json: { user: { username: 'Fixture viewer', role, email, auth_provider: 'jellyfin', password_change_supported: true, password_provider: 'jellyfin' }, activity: { recent: [] } } })
if (url.pathname === '/api/auth/profile/email') { email = payload.email; preference = { ...preference, email, state: 'off' }; return route.fulfill({ json: { email } }) }
if (url.pathname === '/api/profile/email-recaps') return route.fulfill({ json: { ...preference, state: 'enabled', day: 2, resend_after: null } })
if (url.pathname === '/api/profile/newsletters') {
if (request.method() === 'PUT') preference = { ...preference, state: payload.enabled ? 'pending' : 'off', resend_after: payload.enabled ? Date.now()/1000+300 : null }
return route.fulfill({ json: preference })
}
if (url.pathname.startsWith('/api/newsletter-subscription/')) {
if (url.pathname.endsWith('/confirm')) {
if (heldConfirmation) { heldConfirmation.started(); await heldConfirmation.released }
tokenState = payload.action === 'confirm' ? 'enabled' : 'off'
}
return route.fulfill({ json: { action: payload.action, state: tokenState } })
}
if (url.pathname.startsWith('/api/admin/newsletters')) {
if (role !== 'admin') return route.fulfill({ status: role === 'unauthorized' ? 401 : 403, json: { detail: 'Administrator access is required.' } })
if (url.pathname.includes('/artwork/')) return route.fulfill({ contentType: 'image/svg+xml', body: '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="150"><rect width="100" height="150" fill="#655183"/><circle cx="50" cy="65" r="28" fill="#c7bdff"/></svg>' })
if (url.pathname.endsWith('/drafts')) { assert([7, 14, 30].includes(payload.days)); edition = draft(); return route.fulfill({ status: 201, json: edition }) }
if (url.pathname.endsWith('/preview')) {
assert.equal(payload.revision, edition.revision)
if (failPreview) return route.fulfill({ status: 502, json: { detail: 'Jellyfin is temporarily unavailable. Please try again.' } })
return route.fulfill({ json: { ...preview, id: edition.id, revision: edition.revision, subject: edition.subject } })
}
if (url.pathname.endsWith('/test')) {
assert.deepEqual(Object.keys(payload).sort(), ['request_id', 'revision'])
deliveries = [{ id: payload.request_id, subject: edition.subject, username: 'Fixture viewer', email, kind: 'test', state: 'queued', attempts: 0, updated_at: Date.now()/1000, detail: '' }]
return route.fulfill({ status: 202, json: { id: payload.request_id, message: 'Test queued for your confirmed newsletter email.' } })
}
if (url.pathname.endsWith('/publish')) { assert.equal(payload.revision, edition.revision); edition = { ...edition, state: 'scheduled', send_at: payload.send_at ? Date.parse(payload.send_at)/1000 : Date.now()/1000 }; return route.fulfill({ status: 202, json: edition }) }
if (url.pathname.endsWith('/cancel')) { edition = { ...edition, state: 'cancelled' }; return route.fulfill({ json: edition }) }
if (url.pathname.includes('/editions/')) {
if (request.method() === 'PUT') {
if (failSave) return route.fulfill({ status: 409, json: { detail: 'This edition changed. Reload it before continuing.' } })
assert.equal(payload.revision, edition.revision)
assert.deepEqual(Object.keys(payload.titles[0]).sort(), ['featured', 'id', 'selected'])
edition = { ...edition, subject: payload.subject, intro: payload.intro, revision: edition.revision+1, content: { ...edition.content, titles: edition.content.titles.map((title) => ({ ...title, ...payload.titles.find((selection) => selection.id === title.id) })) } }
}
return route.fulfill({ json: edition })
}
if (request.method() === 'PUT') { settings = { ...payload, revision: payload.revision+1, next_send_at: payload.enabled ? Date.now()/1000+86400 : null }; return route.fulfill({ json: settings }) }
return route.fulfill({ json: overview() })
}
if (url.pathname.includes('/events/stream')) return route.fulfill({ contentType: 'text/event-stream', body: ': fixture\n\n' })
return route.fulfill({ json: {} })
})
const page = await context.newPage(), errors = []
page.on('pageerror', (error) => errors.push(error.message))
if (output) fs.mkdirSync(output, { recursive: true })
for (const width of [1440, 980, 390, 320]) {
await page.setViewportSize({ width, height: 1000 })
await page.goto(`${base}/admin/newsletters`)
await page.getByRole('button', { name: 'Create draft', exact: true }).click()
await page.getByLabel('Email subject', { exact: true }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Send newsletter test to me' }).isDisabled(), true)
await page.getByLabel('Feature Arrival', { exact: true }).check()
await page.getByLabel('Announcement', { exact: false }).fill('A weekend of good stories.')
assert.equal(await page.getByRole('button', { name: 'Preview edition', exact: true }).isDisabled(), true)
await page.getByRole('button', { name: 'Save draft', exact: true }).click()
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
const frame = page.frameLocator('iframe[title="Newsletter email preview"]')
await frame.getByRole('heading', { name: 'Whats new on Grizzlyflix', exact: true }).waitFor()
assert.equal(await page.locator('iframe').getAttribute('sandbox'), '')
assert.equal(await frame.locator('img').count(), 2)
assert(await frame.locator('img').evaluateAll((images) => images.every((image) => image.complete && image.naturalWidth > 0)), 'Email posters did not load')
assert.equal(await frame.getByRole('link', { name: 'Watch on Grizzlyflix ↗', exact: true }).first().getAttribute('href'), `https://watch.example.test/web/index.html#!/details?id=${movie}&serverId=${'b'.repeat(32)}`)
assert(await page.locator('.newsletter-poster img').evaluateAll((images) => images.every((image) => image.complete && image.naturalWidth > 0)), 'Editor posters did not load')
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Editor overflow at ${width}`)
assert(await frame.locator('body').evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Email overflow at ${width}`)
if (output) {
await page.locator('iframe').scrollIntoViewIfNeeded()
await page.locator('iframe').screenshot({ path: path.join(output, `newsletter-email-${width}.png`) })
await page.evaluate(() => window.scrollTo(0, 0))
await page.screenshot({ path: path.join(output, `newsletter-editor-${width}.png`), fullPage: true })
}
await page.getByRole('button', { name: 'Plain text', exact: true }).click()
assert.match(await page.locator('.recap-plain-preview').innerText(), /2 new episodes/)
await page.getByRole('button', { name: 'Weekly schedule', exact: true }).click()
await page.getByRole('heading', { name: 'A weekly discovery', exact: true }).waitFor()
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Schedule overflow at ${width}`)
if (output) await page.screenshot({ path: path.join(output, `newsletter-schedule-${width}.png`), fullPage: true })
await page.goto(`${base}/profile#newsletters`)
await page.getByRole('heading', { name: 'New on Grizzlyflix.', exact: true }).waitFor()
assert.equal(await page.locator('#monthly-recaps').getByText('Subscribed', { exact: true }).count(), 1)
assert.equal(await page.locator('#newsletters').getByText('Off', { exact: true }).count(), 1)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Profile overflow at ${width}`)
if (output) await page.locator('#newsletters').screenshot({ path: path.join(output, `newsletter-profile-${width}.png`) })
}
await page.getByRole('button', { name: 'Email me new arrivals', exact: true }).click()
await page.locator('#newsletters').getByText('Check your inbox', { exact: true }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Resend newsletter confirmation' }).isDisabled(), true)
await page.getByRole('button', { name: 'Cancel newsletter subscription' }).click()
await page.locator('#newsletters').getByText('Off', { exact: true }).waitFor()
preference = { ...preference, state: 'enabled' }
await page.getByRole('button', { name: 'Refresh newsletter preference' }).click()
await page.locator('#newsletters').getByText('Subscribed', { exact: true }).waitFor()
await page.getByRole('button', { name: 'Turn off newsletters' }).click()
await page.locator('#newsletters').getByText('Off', { exact: true }).waitFor()
assert.equal(await page.locator('#monthly-recaps').getByText('Subscribed', { exact: true }).count(), 1)
assert.equal(calls.filter((call) => call.path === '/api/profile/email-recaps' && call.method === 'PUT').length, 0)
await page.goto(`${base}/admin/newsletters`)
await page.locator('.newsletter-edition').first().click()
await page.getByLabel('Email subject').fill('Unsaved subject')
failSave = true
await page.getByRole('button', { name: 'Save draft', exact: true }).click()
await page.getByRole('alert').filter({ hasText: 'This edition changed' }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Preview edition', exact: true }).isDisabled(), true)
failSave = false
await page.getByRole('button', { name: 'Discard changes', exact: true }).click()
await page.getByRole('button', { name: 'Reload edition', exact: true }).waitFor()
failPreview = true
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
await page.getByRole('alert').filter({ hasText: 'Jellyfin is temporarily unavailable' }).waitFor()
failPreview = false
await page.getByRole('button', { name: 'Preview edition', exact: true }).click()
await page.locator('iframe').waitFor()
await page.getByRole('button', { name: 'Send newsletter test to me', exact: true }).click()
await page.getByRole('status').filter({ hasText: 'Test queued' }).waitFor()
await page.getByLabel('Schedule for (UTC)', { exact: true }).fill('2026-09-18T09:00')
await page.getByRole('button', { name: 'Schedule edition', exact: true }).click()
await page.getByRole('heading', { name: 'Delivery controls', exact: true }).waitFor()
assert.equal(await page.getByLabel('Email subject').isDisabled(), true)
assert.equal(calls.filter((call) => call.path.endsWith('/publish')).at(-1).payload.send_at, '2026-09-18T09:00:00Z')
await page.getByRole('button', { name: 'Cancel edition', exact: true }).click()
await page.getByRole('status').filter({ hasText: 'Edition cancelled' }).waitFor()
await page.getByRole('button', { name: 'Weekly schedule', exact: true }).click()
await page.getByLabel('Send day').selectOption('5')
await page.getByLabel('Enable automatic weekly newsletters').check()
await page.getByRole('button', { name: 'Save weekly settings', exact: true }).click()
await page.getByText('Weekly sending is on', { exact: true }).waitFor()
assert.equal(settings.weekday, 5)
assert.equal(settings.enabled, true)
deliveries = ['sent', 'retry', 'unknown', 'skipped', 'cancelled'].map((state, index) => ({ ...deliveries[0], id: `fixture-${index}`, state, attempts: 1, detail: 'Fixture delivery result.', next_attempt_at: Date.now()/1000+300 }))
await page.getByRole('button', { name: 'Delivery history', exact: true }).click()
await page.getByRole('button', { name: 'Refresh history', exact: true }).click()
await page.getByText('Needs review', { exact: true }).waitFor()
assert.match(await page.locator('.recap-history').innerText(), /Automatic retries are stopped/)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'History overflows mobile viewport')
for (const width of [1440, 320]) {
await page.setViewportSize({ width, height: 1000 })
tokenState = 'ready'
const before = calls.filter((call) => call.path === '/api/newsletter-subscription/confirm').length
await page.goto(`${base}/newsletter-subscription#action=confirm&token=${'a'.repeat(43)}`)
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).waitFor()
assert.equal(calls.filter((call) => call.path === '/api/newsletter-subscription/confirm').length, before)
assert.equal(await page.locator('.sidebar').count(), 0)
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `Public page overflow at ${width}`)
if (output) await page.screenshot({ path: path.join(output, `newsletter-confirm-${width}.png`), fullPage: true })
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).click()
await page.getByRole('heading', { name: 'Youre on the list.', exact: true }).waitFor()
assert.equal(new URL(page.url()).hash, '')
tokenState = 'ready'
await page.goto(`${base}/newsletter-subscription#action=unsubscribe&token=${'b'.repeat(43)}`)
await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).click()
await page.getByRole('heading', { name: 'Newsletters are turned off.', exact: true }).waitFor()
}
tokenState = 'ready'
let started, release
const waiting = new Promise((resolve) => { started = resolve })
heldConfirmation = { started, released: new Promise((resolve) => { release = resolve }) }
await page.goto(`${base}/newsletter-subscription#action=confirm&token=${'c'.repeat(43)}`)
await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).click()
await waiting
await page.goto(`${base}/newsletter-subscription#action=unsubscribe&token=${'d'.repeat(43)}`)
await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).waitFor()
const applied = page.waitForResponse((response) => response.url().endsWith('/api/newsletter-subscription/confirm'))
heldConfirmation = null
release()
await applied
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))
assert.match(new URL(page.url()).hash, /action=unsubscribe/)
assert.equal(await page.getByRole('button', { name: 'Unsubscribe from newsletters', exact: true }).count(), 1)
await page.goto(`${base}/newsletter-subscription#action=confirm&token=bad`)
await page.getByRole('alert').filter({ hasText: 'This email link is incomplete' }).waitFor()
assert.equal(await page.getByRole('button', { name: 'Confirm newsletter subscription', exact: true }).count(), 0)
role = 'user'
await page.goto(`${base}/admin/newsletters`)
await page.waitForURL(`${base}/`)
role = 'unauthorized'
await page.goto(`${base}/admin/newsletters`)
await page.waitForURL(/\/login\?next=%2Fadmin%2Fnewsletters/)
assert.deepEqual(errors, [])
console.log('Newsletter editor, posters, email layouts, consent, stale edits, scheduling, cancellation, history and access redirects passed at 1440/980/390/320px; all API calls used fixtures.')
await context.close()
} finally { await browser.close() }
})().catch((error) => { console.error(error); process.exitCode = 1 })