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
This commit is contained in:
Brandon Schneider 2026-05-31 14:55:59 -05:00
parent 7e41e620cc
commit 6977d40ebe
5 changed files with 143 additions and 0 deletions

View file

@ -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');
});

View file

@ -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');
});

View file

@ -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'));
});

View file

@ -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());
});

View file

@ -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());
});