Research-Stack/4-Infrastructure/k3s-flake/tests/configure-jellyfin.spec.ts
Brandon Schneider bc0f10db40 feat(infra): Jellyfin wizard complete, SSO-Auth plugin files deployed
- Jellyfin setup wizard completed, admin account created (admin/jellyfin-admin)
- SSO-Auth plugin v4.0.0.3 downloaded and placed in plugins directory
- Authentik OIDC provider for Jellyfin updated with correct redirect URI
- All services verified running

Build: 3313 jobs, 0 errors (lake build Compiler)
2026-05-31 14:14:20 -05:00

146 lines
6.1 KiB
TypeScript

import { test, expect } from '@playwright/test';
test('Log in to Jellyfin and configure OIDC plugin', async ({ browser }) => {
const context = await browser.newContext({ storageState: 'auth-state.json' });
const page = await context.newPage();
console.log('Navigating to Jellyfin...');
await page.goto('https://media.researchstack.info/');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(4000);
console.log('Current URL:', page.url());
// Fill Jellyfin login details
const usernameInput = page.locator('input[type="text"], input#txtManualName');
const passwordInput = page.locator('input[type="password"], input#txtManualPassword');
if (await usernameInput.isVisible()) {
console.log('Logging into Jellyfin manually...');
await usernameInput.fill('admin');
await passwordInput.fill('RY03KhsFez73K5va2uUb');
await page.locator('button[type="submit"], button.button-submit').first().click();
await page.waitForTimeout(6000);
}
console.log('Logged in. URL:', page.url());
await page.screenshot({ path: 'jellyfin-home.png' });
// Navigate directly to Plugins page
console.log('Navigating directly to Plugins page...');
await page.goto('https://media.researchstack.info/web/#/plugins.html');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(5000);
console.log('Plugins URL:', page.url());
await page.screenshot({ path: 'jellyfin-plugins.png' });
const textContent = await page.evaluate(() => document.body.innerText);
console.log('Plugins page text:', textContent.slice(0, 1000));
if (textContent.includes('OpenID Connect')) {
console.log('OIDC Plugin is already installed!');
} else {
console.log('OIDC Plugin not found in installed list. Navigating to Catalog...');
// Click on Catalog tab
const catalogTab = page.locator('button:has-text("Catalog"), a:has-text("Catalog"), [role="tab"]:has-text("Catalog")').first();
await catalogTab.click();
await page.waitForTimeout(5000);
await page.screenshot({ path: 'jellyfin-catalog.png' });
// Search for OIDC or look at the list
console.log('Searching for OpenID Connect in catalog...');
const oidcLink = page.locator('a:has-text("OpenID Connect"), [role="button"]:has-text("OpenID Connect")').first();
if (await oidcLink.isVisible()) {
await oidcLink.click();
await page.waitForTimeout(4000);
await page.screenshot({ path: 'jellyfin-plugin-details.png' });
// Click Install
const installBtn = page.locator('button:has-text("Install"), button.button-submit').first();
await installBtn.click();
await page.waitForTimeout(4000);
// Handle OK button in modal dialog if any
const okBtn = page.locator('button:has-text("OK")').first();
if (await okBtn.isVisible()) {
await okBtn.click();
await page.waitForTimeout(2000);
}
console.log('OIDC Plugin installation triggered. Need to restart pod!');
// Let's exit the test and let the runner restart the pod
await context.close();
return;
} else {
console.log('Could not find OpenID Connect in catalog.');
await context.close();
throw new Error('OIDC Plugin not found in catalog');
}
}
// If we reach here, OIDC plugin is installed. Let's configure it.
console.log('Navigating to OIDC configuration page...');
// Click on "OpenID Connect" plugin row/link in the installed list
const oidcConfigLink = page.locator('a:has-text("OpenID Connect"), [role="button"]:has-text("OpenID Connect")').first();
await oidcConfigLink.click();
await page.waitForTimeout(5000);
console.log('OIDC Configuration URL:', page.url());
await page.screenshot({ path: 'jellyfin-oidc-config-initial.png' });
// In the OpenID Connect settings form, fill in values:
// Enabled checkbox, Client ID, Client Secret, OIDC Authority
// Let's inspect the input fields
console.log('Filling OIDC config fields...');
// Let's log all input fields on the page to see what's there
const inputs = await page.evaluate(() => {
return Array.from(document.querySelectorAll('input, select, textarea')).map(el => ({
id: el.id,
name: el.getAttribute('name'),
type: el.getAttribute('type'),
value: (el as HTMLInputElement).value,
label: el.labels?.[0]?.innerText || ''
}));
});
console.log('Found form inputs:', inputs);
// Enabled checkbox: usually has an id or label containing "Enable" or "Enabled"
const enabledCheckbox = page.locator('input[type="checkbox"], input#chkEnableOpenId').first();
const isChecked = await enabledCheckbox.isChecked();
if (!isChecked) {
await enabledCheckbox.click();
}
// OIDC Authority / Issuer
const authorityInput = page.locator('input[label*="Authority"], input[id*="Authority"], input[name*="Authority"], input[placeholder*="Authority"]').first();
if (await authorityInput.isVisible()) {
await authorityInput.fill('https://auth.researchstack.info/application/o/jellyfin/');
} else {
// Try by index or other attributes
const firstTextInput = page.locator('input[type="text"]').first();
await firstTextInput.fill('https://auth.researchstack.info/application/o/jellyfin/');
}
// Client ID
const clientIdInput = page.locator('input[label*="Client ID"], input[id*="ClientId"], input[name*="ClientId"]').first();
if (await clientIdInput.isVisible()) {
await clientIdInput.fill('sso-jellyfin');
}
// Client Secret
const clientSecretInput = page.locator('input[label*="Secret"], input[id*="Secret"], input[name*="Secret"]').first();
if (await clientSecretInput.isVisible()) {
await clientSecretInput.fill('fc96e87934f544bef43e5466c929baa73b681c4e5d0aa9fc5cafe6e6a36e2e49');
}
await page.screenshot({ path: 'jellyfin-oidc-config-filled.png' });
// Click Save
const saveBtn = page.locator('button[type="submit"], button:has-text("Save"), button.button-submit').first();
await saveBtn.click();
await page.waitForTimeout(4000);
await page.screenshot({ path: 'jellyfin-oidc-config-saved.png' });
console.log('OIDC Configuration saved.');
await context.close();
});