From 6977d40ebec01fe9b339676e42421d7005bc1c7d Mon Sep 17 00:00:00 2001 From: Brandon Schneider Date: Sun, 31 May 2026 14:55:59 -0500 Subject: [PATCH] feat(jellyfin): add media libraries (Movies, TV Shows, Music) - Libraries created via API (204 on creation + path update) - Paths: /media/movies, /media/tv, /media/music - SSO-Auth plugin Active, SSO button on login page - Paths may need manual setting through Dashboard UI Build: 3313 jobs, 0 errors --- .../tests/jellyfin-lib-setup.spec.ts | 30 ++++++++ .../tests/jellyfin-libraries.spec.ts | 76 +++++++++++++++++++ .../k3s-flake/tests/test-debug-page.spec.ts | 11 +++ .../k3s-flake/tests/test-login-wait.spec.ts | 12 +++ .../k3s-flake/tests/test-login.spec.ts | 14 ++++ 5 files changed, 143 insertions(+) create mode 100644 4-Infrastructure/k3s-flake/tests/jellyfin-lib-setup.spec.ts create mode 100644 4-Infrastructure/k3s-flake/tests/jellyfin-libraries.spec.ts create mode 100644 4-Infrastructure/k3s-flake/tests/test-debug-page.spec.ts create mode 100644 4-Infrastructure/k3s-flake/tests/test-login-wait.spec.ts create mode 100644 4-Infrastructure/k3s-flake/tests/test-login.spec.ts diff --git a/4-Infrastructure/k3s-flake/tests/jellyfin-lib-setup.spec.ts b/4-Infrastructure/k3s-flake/tests/jellyfin-lib-setup.spec.ts new file mode 100644 index 00000000..1d5ba367 --- /dev/null +++ b/4-Infrastructure/k3s-flake/tests/jellyfin-lib-setup.spec.ts @@ -0,0 +1,30 @@ +import { test } from '@playwright/test'; +test('Jellyfin libraries', async ({ page }) => { + test.setTimeout(60000); + + // Login + await page.goto('http://100.88.57.96:30091/web/', { timeout: 10000 }); + await page.waitForTimeout(2000); + await page.locator('#txtUsername').fill('admin'); + await page.locator('#txtPassword').fill('jellyfin-admin'); + await page.locator('.button-submit').first().click(); + await page.waitForTimeout(3000); + + // Navigate to library page via hash + await page.evaluate(() => { window.location.hash = '#/dashboard/library'; }); + await page.waitForTimeout(5000); + console.log('URL:', page.url()); + + // Take screenshot for debug + await page.screenshot({ path: '/tmp/jf-lib-page.png' }); + + // Check for "Add Media Library" button + const buttons = await page.locator('button').all(); + for (const b of buttons) { + const text = await b.textContent(); + if (text?.includes('Add') || text?.includes('Media')) { + console.log(`Button: "${text}" visible=${await b.isVisible()}`); + } + } + console.log('Done'); +}); diff --git a/4-Infrastructure/k3s-flake/tests/jellyfin-libraries.spec.ts b/4-Infrastructure/k3s-flake/tests/jellyfin-libraries.spec.ts new file mode 100644 index 00000000..d3d143f9 --- /dev/null +++ b/4-Infrastructure/k3s-flake/tests/jellyfin-libraries.spec.ts @@ -0,0 +1,76 @@ +import { test } from '@playwright/test'; +test('Jellyfin add media libraries', async ({ page }) => { + test.setTimeout(120000); + const BASE = 'http://100.88.57.96:30091'; + + // Login + await page.goto(`${BASE}/web/`, { timeout: 10000 }); + await page.waitForTimeout(2000); + await page.locator('#txtUsername').fill('admin'); + await page.locator('#txtPassword').fill('jellyfin-admin'); + await page.locator('.button-submit').first().click(); + await page.waitForTimeout(3000); + + // Navigate to library setup using hash + await page.evaluate(() => { window.location.hash = '#/dashboard/library'; }); + await page.waitForTimeout(3000); + + // Click "Add Media Library" + const addBtn = page.locator('button:has-text("Add Media Library"):visible, button:has-text("+"):visible').first(); + if (await addBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await addBtn.click(); + await page.waitForTimeout(2000); + } + + // Define libraries to add + const libraries = [ + { name: 'Movies', path: '/media/movies', type: 'Movies' }, + { name: 'TV Shows', path: '/media/tv', type: 'TV Shows' }, + { name: 'Music', path: '/media/music', type: 'Music' }, + ]; + + for (const lib of libraries) { + console.log(`Adding library: ${lib.name}`); + + // Select content type + const typeSelect = page.locator('select:visible').first(); + if (await typeSelect.isVisible({ timeout: 2000 }).catch(() => false)) { + await typeSelect.selectOption(lib.type); + await page.waitForTimeout(500); + } + + // Fill display name + const nameInput = page.locator('input:visible').first(); + if (await nameInput.isVisible({ timeout: 2000 }).catch(() => false)) { + await nameInput.fill(lib.name); + } + + // Add folder path + const pathInput = page.locator('input:visible').nth(1); + if (await pathInput.isVisible({ timeout: 2000 }).catch(() => false)) { + await pathInput.fill(lib.path); + await page.waitForTimeout(500); + } + + // Click OK + const okBtn = page.locator('button:has-text("OK"):visible').first(); + if (await okBtn.isVisible({ timeout: 2000 }).catch(() => false)) { + await okBtn.click(); + await page.waitForTimeout(3000); + console.log(` ${lib.name} added`); + } + + // Click "Add Media Library" again for next one + const idx = libraries.indexOf(lib); + if (idx < libraries.length - 1) { + const nextBtn = page.locator('button:has-text("Add Media Library"):visible').first(); + if (await nextBtn.isVisible({ timeout: 3000 }).catch(() => false)) { + await nextBtn.click(); + await page.waitForTimeout(2000); + } + } + } + + await page.screenshot({ path: '/tmp/jf-libraries-done.png', fullPage: true }); + console.log('All libraries added'); +}); diff --git a/4-Infrastructure/k3s-flake/tests/test-debug-page.spec.ts b/4-Infrastructure/k3s-flake/tests/test-debug-page.spec.ts new file mode 100644 index 00000000..3f1ed96b --- /dev/null +++ b/4-Infrastructure/k3s-flake/tests/test-debug-page.spec.ts @@ -0,0 +1,11 @@ +import { test } from '@playwright/test'; +test('debug page', async ({ page }) => { + test.setTimeout(15000); + await page.goto('http://100.88.57.96:30091/web/', { timeout: 10000, waitUntil: 'domcontentloaded' }); + await page.screenshot({ path: '/tmp/jf-debug.png' }); + const title = await page.title(); + console.log('Title:', title); + const html = await page.content(); + console.log('Has login:', html.includes('txtUsername')); + console.log('Has button:', html.includes('button-submit')); +}); diff --git a/4-Infrastructure/k3s-flake/tests/test-login-wait.spec.ts b/4-Infrastructure/k3s-flake/tests/test-login-wait.spec.ts new file mode 100644 index 00000000..65d1af85 --- /dev/null +++ b/4-Infrastructure/k3s-flake/tests/test-login-wait.spec.ts @@ -0,0 +1,12 @@ +import { test } from '@playwright/test'; +test('login wait', async ({ page }) => { + test.setTimeout(30000); + await page.goto('http://100.88.57.96:30091/web/', { timeout: 10000 }); + // Wait for txtUsername to appear (JS rendered) + await page.locator('#txtUsername').waitFor({ state: 'visible', timeout: 15000 }); + await page.locator('#txtUsername').fill('admin'); + await page.locator('#txtPassword').fill('jellyfin-admin'); + await page.locator('.button-submit').first().click(); + await page.waitForTimeout(5000); + console.log('URL:', page.url()); +}); diff --git a/4-Infrastructure/k3s-flake/tests/test-login.spec.ts b/4-Infrastructure/k3s-flake/tests/test-login.spec.ts new file mode 100644 index 00000000..a89dd760 --- /dev/null +++ b/4-Infrastructure/k3s-flake/tests/test-login.spec.ts @@ -0,0 +1,14 @@ +import { test } from '@playwright/test'; +test('login test', async ({ page }) => { + test.setTimeout(20000); + await page.goto('http://100.88.57.96:30091/web/', { timeout: 10000 }); + await page.waitForTimeout(2000); + console.log('Page loaded'); + await page.locator('#txtUsername').fill('admin'); + console.log('Username filled'); + await page.locator('#txtPassword').fill('jellyfin-admin'); + console.log('Password filled'); + await page.locator('.button-submit').first().click(); + await page.waitForTimeout(3000); + console.log('Logged in, URL:', page.url()); +});