test(ui): stub the pin routes in the add-on e2e fixtures (#12882)

test(ui): stub the add-on routes in the add-on e2e fixtures

The add-on owner now runs as its own workload. With no owner running the
server reports the add-ons feature off and answers 5xx on
/api/addons/builtin and /api/addons/pins. The add-on pages are gated on
that flag, and the navigation shell calls both routes, which the browser
logs as failed requests. Specs that draw add-on pages assert no console
errors, so they failed on master.

Stub those routes on the browser context: the feature flag is switched on
with the server's other flags kept, and the pin routes answer with an empty
set. A spec's own page routes still take precedence. Studio's old-address
and 404 specs call the same stub, since they expect add-ons to be on. The
server behaviour is unchanged.

Assisted-by: Claude Code:claude-opus-5-5

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
mudler-agentandEttore Di Giacinto authored and GitHub committed 2026-10-24 22:46:09 +02:00
1 parent 48478a89cd
commit b9b5434692
4 files changed
+23 -1

No files matched your search

+17
View File
@@ -44,6 +44,22 @@ export function sessionFor(user, permissions = {}) {
const ok = (payload) => ({ status: 200, contentType: 'application/json', body: JSON.stringify({ payload }) })
const fail = (status, type, extra = {}) => ({ status, contentType: 'application/json', body: JSON.stringify({ error: { type, message: type, ...extra } }) })
// Specs that draw add-on pages need add-ons on. With no add-on owner running,
// the server reports add-ons off and answers 5xx on the pin routes, and the
// browser logs each failed request. These stubs answer the add-on pages with an
// empty set. They live on the context, which Playwright matches after the
// page's own routes: a spec that sets its own features, builtin or pin list
// still gets it.
export async function stubAddonRoutes(page) {
// The real flags, with add-ons on: the other switches keep what the server says.
await page.context().route('**/api/features', async route => {
const real = await (await route.fetch()).json()
return route.fulfill({ json: { ...real, addons: true } })
})
await page.context().route('**/api/addons/builtin', route => route.fulfill({ json: { builtin: [] } }))
await page.context().route('**/api/addons/pins', route => route.fulfill({ json: [] }))
}
// Installs the routes and returns the mutable state the spec reads and edits.
export async function mockAddons(page, options = {}) {
const state = {
@@ -62,6 +78,7 @@ export async function mockAddons(page, options = {}) {
dispatch: options.dispatch || null,
}
await stubAddonRoutes(page)
await page.route(/\/api\/addons\/apps\/[^/]+\/icon/, route => route.fulfill({ status: 200, contentType: 'image/png', body: PNG }))
await page.route(/\/api\/addons\/apps$/, route => {
state.log.appsList += 1
+2
View File
@@ -1,8 +1,10 @@
import { test, expect } from './coverage-fixtures.js'
import { mockAccess } from './access-fixtures.js'
import { stubAddonRoutes } from './addons-fixtures.js'
test.describe('404', () => {
test('names the address, offers a way home and lists the places the viewer can go', async ({ page }) => {
await stubAddonRoutes(page)
await page.goto('/app/no-such-page')
const nf = page.getByTestId('not-found')
await expect(nf.getByRole('heading', { level: 1 })).toHaveText('This page does not exist')
+2 -1
View File
@@ -6,7 +6,7 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { app, mockAddons } from './addons-fixtures.js'
import { app, mockAddons, stubAddonRoutes } from './addons-fixtures.js'
const addons = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../addons')
const DIGEST = 'e'.repeat(64)
@@ -55,6 +55,7 @@ export function packagedApp(id, over = {}) {
// Returns the state of mockAddons.
export async function installStudioAddons(page, { ids = ['studio', 'talk'], ...options } = {}) {
const dirs = await Promise.all(ids.map(build))
await stubAddonRoutes(page)
await page.route(/\/api\/addons\/(studio|talk|voice)\/assets\//, route => {
const parts = new URL(route.request().url()).pathname.split('/')
const id = parts[3]
@@ -1,5 +1,6 @@
import { test, expect } from './coverage-fixtures.js'
import { installStudioAddons } from './studio-addon.js'
import { stubAddonRoutes } from './addons-fixtures.js'
import { mockStudio } from './studio-fixtures.js'
import { fakeRealtime } from './chat-fixtures.js'
@@ -66,6 +67,7 @@ test.describe('Studio add-on', () => {
})
test('the old Studio addresses say so plainly when the add-on is not installed', async ({ page }) => {
await stubAddonRoutes(page)
for (const address of ['/app/studio', '/app/studio/tts', '/app/image', '/app/talk']) {
await page.goto(address)
await expect(page.getByTestId('addon-unavailable')).toBeVisible({ timeout: 15_000 })