727 lines
28 KiB
JavaScript
727 lines
28 KiB
JavaScript
/**
|
|
* Settings Panel Tests
|
|
*
|
|
* Tests the Doctor left sidebar settings panel functionality:
|
|
* - Settings panel toggle
|
|
* - Language selection
|
|
* - Provider selection
|
|
* - Settings persistence
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { test, expect } from '@playwright/test';
|
|
import { waitForDoctorReady, waitForI18nLoaded, clearStorage } from '../utils/helpers.js';
|
|
|
|
const UI_TEXT = JSON.parse(
|
|
fs.readFileSync(path.resolve(process.cwd(), 'tests/e2e/mocks/ui-text.json'), 'utf-8')
|
|
);
|
|
|
|
test.describe('Settings Panel', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Mock the ComfyUI modules that doctor.js tries to import
|
|
await page.route('**/scripts/app.js', route => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/javascript',
|
|
body: 'export const app = window.app;',
|
|
});
|
|
});
|
|
|
|
await page.route('**/scripts/api.js', route => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/javascript',
|
|
body: 'export const api = window.api;',
|
|
});
|
|
});
|
|
|
|
// Mock backend API endpoints
|
|
await page.route('**/doctor/provider_defaults', route => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
openai: 'https://api.openai.com/v1',
|
|
deepseek: 'https://api.deepseek.com/v1',
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route('**/doctor/ui_text*', route => {
|
|
const url = new URL(route.request().url());
|
|
const lang = url.searchParams.get('lang') || 'en';
|
|
const text = UI_TEXT[lang] || UI_TEXT.en || {};
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ language: lang, text }),
|
|
});
|
|
});
|
|
|
|
await page.route('**/debugger/set_language', route => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true }),
|
|
});
|
|
});
|
|
|
|
await page.goto('test-harness.html');
|
|
await clearStorage(page);
|
|
|
|
await waitForDoctorReady(page);
|
|
await waitForI18nLoaded(page);
|
|
});
|
|
|
|
test('should register Doctor settings declaratively before setup logic runs', async ({ page }) => {
|
|
const settings = await page.evaluate(() => ({
|
|
enabled: window.app.extensionManager.setting.get('Doctor.General.Enable'),
|
|
language: window.app.extensionManager.setting.get('Doctor.General.Language'),
|
|
autoOpen: window.app.extensionManager.setting.get('Doctor.Behavior.AutoOpenOnError'),
|
|
}));
|
|
|
|
expect(settings.enabled).toBe(true);
|
|
expect(settings.language).toBe('en');
|
|
expect(settings.autoOpen).toBe(true);
|
|
});
|
|
|
|
test('every registered Doctor setting disables host change telemetry', async ({ page }) => {
|
|
const contract = await page.evaluate(async () => {
|
|
const { DOCTOR_EXTENSION_SETTINGS } = await import('/web/comfyui_frontend_compat.js');
|
|
return DOCTOR_EXTENSION_SETTINGS.map((setting) => ({
|
|
id: setting.id,
|
|
trackChanges: setting.telemetry?.trackChanges,
|
|
telemetryKeyCount: Object.keys(setting.telemetry || {}).length,
|
|
}));
|
|
});
|
|
|
|
expect(contract).toHaveLength(11);
|
|
expect(contract.map((setting) => setting.id)).not.toContain('Doctor.LLM.ApiKey');
|
|
expect(contract.every(
|
|
(setting) => setting.trackChanges === false && setting.telemetryKeyCount === 1
|
|
)).toBe(true);
|
|
});
|
|
|
|
test('host sidebar API mock exposes current and deprecated wrappers', async ({ page }) => {
|
|
const sidebarApi = await page.evaluate(() => {
|
|
const current = window.app.extensionManager.sidebarTab;
|
|
const legacy = window.app.extensionManager;
|
|
const before = current.sidebarTabs.length;
|
|
|
|
legacy.registerSidebarTab({
|
|
id: 'legacy-host-compat',
|
|
title: 'Legacy Host Compat',
|
|
type: 'custom',
|
|
render() {},
|
|
});
|
|
current.registerSidebarTab({
|
|
id: 'current-host-compat',
|
|
title: 'Current Host Compat',
|
|
type: 'custom',
|
|
render() {},
|
|
});
|
|
|
|
const ids = current.sidebarTabs.map((tab) => tab.id);
|
|
const deprecatedReadsSameRegistry = legacy.getSidebarTabs().length === current.sidebarTabs.length;
|
|
|
|
legacy.unregisterSidebarTab('legacy-host-compat');
|
|
current.unregisterSidebarTab('current-host-compat');
|
|
|
|
return {
|
|
hasCurrentRegister: typeof current.registerSidebarTab === 'function',
|
|
hasDeprecatedRegister: typeof legacy.registerSidebarTab === 'function',
|
|
ids,
|
|
deprecatedReadsSameRegistry,
|
|
after: current.sidebarTabs.length,
|
|
before,
|
|
};
|
|
});
|
|
|
|
expect(sidebarApi.hasCurrentRegister).toBe(true);
|
|
expect(sidebarApi.hasDeprecatedRegister).toBe(true);
|
|
expect(sidebarApi.ids).toContain('legacy-host-compat');
|
|
expect(sidebarApi.ids).toContain('current-host-compat');
|
|
expect(sidebarApi.deprecatedReadsSameRegistry).toBe(true);
|
|
expect(sidebarApi.after).toBe(sidebarApi.before);
|
|
});
|
|
|
|
test('should register Doctor sidebar through current sidebar API when present', async ({ page }) => {
|
|
const registration = await page.evaluate(() => {
|
|
const calls = window.app.__getSidebarRegistrationCalls()
|
|
.filter((call) => call.id === 'comfyui-doctor');
|
|
const sidebarTabs = window.app.extensionManager.sidebarTab.sidebarTabs
|
|
.filter((tab) => tab.id === 'comfyui-doctor');
|
|
return {
|
|
lastSource: calls.at(-1)?.source,
|
|
callCount: calls.length,
|
|
tabCount: sidebarTabs.length,
|
|
};
|
|
});
|
|
|
|
expect(registration.lastSource).toBe('extensionManager.sidebarTab');
|
|
expect(registration.callCount).toBe(1);
|
|
expect(registration.tabCount).toBe(1);
|
|
});
|
|
|
|
test('should fall back to deprecated sidebar wrapper when current API is absent', async ({ page }) => {
|
|
await page.goto('test-harness.html?sidebarApi=deprecated-only');
|
|
await waitForDoctorReady(page);
|
|
await waitForI18nLoaded(page);
|
|
|
|
const registration = await page.evaluate(() => {
|
|
const calls = window.app.__getSidebarRegistrationCalls()
|
|
.filter((call) => call.id === 'comfyui-doctor');
|
|
const sidebarTabs = window.app.extensionManager.getSidebarTabs()
|
|
.filter((tab) => tab.id === 'comfyui-doctor');
|
|
return {
|
|
hasCurrentApi: Boolean(window.app.extensionManager.sidebarTab),
|
|
lastSource: calls.at(-1)?.source,
|
|
callCount: calls.length,
|
|
tabCount: sidebarTabs.length,
|
|
};
|
|
});
|
|
|
|
expect(registration.hasCurrentApi).toBe(false);
|
|
expect(registration.lastSource).toBe('extensionManager.registerSidebarTab');
|
|
expect(registration.callCount).toBe(1);
|
|
expect(registration.tabCount).toBe(1);
|
|
await expect(page.locator('.doctor-tab-button[data-tab-id="settings"]')).toBeVisible();
|
|
});
|
|
|
|
test('should have settings tab button', async ({ page }) => {
|
|
const toggleBtn = page.locator('.doctor-tab-button[data-tab-id="settings"]');
|
|
await expect(toggleBtn).toBeVisible();
|
|
});
|
|
|
|
test('should toggle settings panel visibility via tab', async ({ page }) => {
|
|
const settingsPanel = page.locator('#doctor-settings-panel');
|
|
const settingsTabBtn = page.locator('.doctor-tab-button[data-tab-id="settings"]');
|
|
const chatTabBtn = page.locator('.doctor-tab-button[data-tab-id="chat"]');
|
|
|
|
// Panel should be hidden (or non-existent) by default as Chat is default tab
|
|
await expect(settingsPanel).toBeHidden();
|
|
|
|
// Click Settings Tab to show
|
|
await settingsTabBtn.click();
|
|
await expect(settingsPanel).toBeVisible();
|
|
|
|
// Click Chat Tab to hide Settings
|
|
await chatTabBtn.click();
|
|
await expect(settingsPanel).toBeHidden();
|
|
});
|
|
|
|
test('should display language selector', async ({ page }) => {
|
|
// Open settings panel
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const languageSelect = page.locator('#doctor-language-select');
|
|
await expect(languageSelect).toBeVisible();
|
|
await expect(languageSelect).toBeEnabled();
|
|
});
|
|
|
|
test('should have all supported languages in selector', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const languageSelect = page.locator('#doctor-language-select');
|
|
const options = await languageSelect.locator('option').allTextContents();
|
|
|
|
// Verify all 9 languages are present
|
|
const expectedLanguages = ['English', '繁體中文', '简体中文', '日本語', 'Deutsch', 'Français', 'Italiano', 'Español', '한국어'];
|
|
|
|
for (const lang of expectedLanguages) {
|
|
const hasLang = options.some(opt => opt.includes(lang));
|
|
expect(hasLang).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('should change language selection', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const languageSelect = page.locator('#doctor-language-select');
|
|
|
|
// Get initial value
|
|
const initialValue = await languageSelect.inputValue();
|
|
|
|
// Change to Japanese
|
|
await languageSelect.selectOption('ja');
|
|
|
|
// Verify value changed
|
|
const newValue = await languageSelect.inputValue();
|
|
expect(newValue).toBe('ja');
|
|
expect(newValue).not.toBe(initialValue);
|
|
});
|
|
|
|
test('should display provider selector', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const providerSelect = page.locator('#doctor-provider-select');
|
|
await expect(providerSelect).toBeVisible();
|
|
await expect(providerSelect).toBeEnabled();
|
|
});
|
|
|
|
test('should have multiple AI providers', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const providerSelect = page.locator('#doctor-provider-select');
|
|
const options = await providerSelect.locator('option').allTextContents();
|
|
|
|
// Should have at least these providers
|
|
expect(options.length).toBeGreaterThan(5);
|
|
expect(options.some(opt => opt.includes('OpenAI'))).toBe(true);
|
|
expect(options.some(opt => opt.includes('DeepSeek'))).toBe(true);
|
|
});
|
|
|
|
test('should display API key input', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const apiKeyInput = page.locator('#doctor-apikey-input');
|
|
await expect(apiKeyInput).toBeVisible();
|
|
await expect(apiKeyInput).toHaveAttribute('type', 'password');
|
|
});
|
|
|
|
test('should display base URL input', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const baseUrlInput = page.locator('#doctor-baseurl-input');
|
|
await expect(baseUrlInput).toBeVisible();
|
|
});
|
|
|
|
test('should display save button', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
await expect(saveBtn).toBeVisible();
|
|
await expect(saveBtn).toBeEnabled();
|
|
});
|
|
|
|
test('should save settings and show feedback', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
// Change language
|
|
const languageSelect = page.locator('#doctor-language-select');
|
|
await languageSelect.selectOption('ja');
|
|
|
|
// Click save
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
const originalText = await saveBtn.textContent();
|
|
await saveBtn.click();
|
|
|
|
await expect(saveBtn).toContainText('Saved');
|
|
});
|
|
|
|
test('should persist active tab state', async ({ page }) => {
|
|
// Open settings panel
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
// Verify it's open
|
|
await expect(page.locator('#doctor-settings-panel')).toBeVisible();
|
|
|
|
// Reload page
|
|
await page.goto('test-harness.html');
|
|
await waitForDoctorReady(page);
|
|
|
|
// Panel should remember open state
|
|
const settingsPanel = page.locator('#doctor-settings-panel');
|
|
const isVisible = await settingsPanel.isVisible();
|
|
|
|
// Check localStorage was set
|
|
const storedState = await page.evaluate(() => {
|
|
return localStorage.getItem('doctor_active_tab');
|
|
});
|
|
expect(storedState).toBe('settings');
|
|
});
|
|
|
|
// F17: Auto-open toggle tests
|
|
test('should display auto-open toggle checkbox', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
await expect(autoOpenToggle).toBeVisible();
|
|
await expect(autoOpenToggle).toHaveAttribute('type', 'checkbox');
|
|
});
|
|
|
|
test('should have auto-open toggle checked by default (new installs)', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
// Default for new installs should be checked (true)
|
|
await expect(autoOpenToggle).toBeChecked();
|
|
});
|
|
|
|
test('should display auto-open toggle label with i18n text', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
// Look for the label text near the checkbox
|
|
const labelText = page.locator('label:has(#doctor-auto-open-toggle)');
|
|
await expect(labelText).toContainText('Auto-open error report panel');
|
|
});
|
|
|
|
test('should toggle auto-open checkbox state', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
|
|
// Initially checked (default true)
|
|
await expect(autoOpenToggle).toBeChecked();
|
|
|
|
// Click to uncheck
|
|
await autoOpenToggle.click();
|
|
await expect(autoOpenToggle).not.toBeChecked();
|
|
|
|
// Click to check again
|
|
await autoOpenToggle.click();
|
|
await expect(autoOpenToggle).toBeChecked();
|
|
});
|
|
|
|
test('should save auto-open toggle state', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
|
|
// Uncheck the toggle
|
|
await autoOpenToggle.click();
|
|
await expect(autoOpenToggle).not.toBeChecked();
|
|
|
|
// Save settings
|
|
await saveBtn.click();
|
|
await expect(saveBtn).toContainText('Saved');
|
|
|
|
// Verify setting was stored
|
|
const storedValue = await page.evaluate(() => {
|
|
return window.app.extensionManager.setting.get('Doctor.Behavior.AutoOpenOnError');
|
|
});
|
|
expect(storedValue).toBe(false);
|
|
});
|
|
|
|
test('should persist auto-open toggle state after reload', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
|
|
// Uncheck the toggle and save
|
|
await autoOpenToggle.click();
|
|
await saveBtn.click();
|
|
await expect(saveBtn).toContainText('Saved');
|
|
|
|
// Reload page
|
|
await page.goto('test-harness.html');
|
|
await waitForDoctorReady(page);
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
// Verify toggle state persisted
|
|
const reloadedToggle = page.locator('#doctor-auto-open-toggle');
|
|
await expect(reloadedToggle).not.toBeChecked();
|
|
});
|
|
|
|
test('should apply auto-open setting immediately to DoctorUI', async ({ page }) => {
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
|
|
// Uncheck and save
|
|
await autoOpenToggle.click();
|
|
await saveBtn.click();
|
|
|
|
// Verify DoctorUI instance was updated immediately
|
|
const doctorAutoOpen = await page.evaluate(() => {
|
|
return window.app.Doctor?.autoOpenOnError;
|
|
});
|
|
expect(doctorAutoOpen).toBe(false);
|
|
});
|
|
|
|
// F17: Behavior tests - verify panel auto-open on new error
|
|
test('should auto-open right panel when enabled and error occurs', async ({ page }) => {
|
|
// Ensure auto-open is ON (default)
|
|
const isAutoOpenOn = await page.evaluate(() => {
|
|
return window.app.Doctor?.autoOpenOnError === true;
|
|
});
|
|
expect(isAutoOpenOn).toBe(true);
|
|
|
|
// Close the panel first if it's open
|
|
await page.evaluate(() => {
|
|
const panel = document.getElementById('doctor-sidebar');
|
|
if (panel) {
|
|
panel.classList.remove('visible');
|
|
window.app.Doctor.isVisible = false;
|
|
}
|
|
});
|
|
|
|
// Verify panel is closed
|
|
const panelBefore = page.locator('#doctor-sidebar');
|
|
await expect(panelBefore).not.toHaveClass(/visible/);
|
|
|
|
// Trigger a new error
|
|
await page.evaluate(() => {
|
|
window.app.Doctor.handleNewError({
|
|
last_error: 'RuntimeError: CUDA out of memory',
|
|
timestamp: new Date().toISOString(),
|
|
node_context: { node_id: '42', node_name: 'KSampler', node_class: 'KSampler' }
|
|
});
|
|
});
|
|
|
|
// Panel should auto-open
|
|
await expect(panelBefore).toHaveClass(/visible/, { timeout: 2000 });
|
|
});
|
|
|
|
test('should NOT auto-open right panel when disabled and error occurs', async ({ page }) => {
|
|
// Disable auto-open via settings
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
const autoOpenToggle = page.locator('#doctor-auto-open-toggle');
|
|
const saveBtn = page.locator('#doctor-save-settings-btn');
|
|
await autoOpenToggle.click();
|
|
await saveBtn.click();
|
|
await expect(saveBtn).toContainText('Saved');
|
|
|
|
// Verify auto-open is OFF
|
|
const isAutoOpenOff = await page.evaluate(() => {
|
|
return window.app.Doctor?.autoOpenOnError === false;
|
|
});
|
|
expect(isAutoOpenOff).toBe(true);
|
|
|
|
// Close the panel
|
|
await page.evaluate(() => {
|
|
const panel = document.getElementById('doctor-sidebar');
|
|
if (panel) {
|
|
panel.classList.remove('visible');
|
|
window.app.Doctor.isVisible = false;
|
|
}
|
|
});
|
|
|
|
// Verify panel is closed
|
|
const panelBefore = page.locator('#doctor-sidebar');
|
|
await expect(panelBefore).not.toHaveClass(/visible/);
|
|
|
|
// Trigger a new error
|
|
await page.evaluate(() => {
|
|
window.app.Doctor.handleNewError({
|
|
last_error: 'ValueError: invalid input shape',
|
|
timestamp: new Date().toISOString(),
|
|
node_context: { node_id: '99', node_name: 'VAEDecode', node_class: 'VAEDecode' }
|
|
});
|
|
});
|
|
|
|
// Panel should remain closed
|
|
await page.waitForTimeout(500); // Give time for any async updates
|
|
await expect(panelBefore).not.toHaveClass(/visible/);
|
|
});
|
|
|
|
test('plain dynamic status keeps synchronous and asynchronous tab errors literal', async ({ page }) => {
|
|
const result = await page.evaluate(async () => {
|
|
const payload = '<span id="dynamic-status-tab-marker">tab marker</span>';
|
|
const { TabManager, TabRegistry } = await import('/web/doctor_tabs.js');
|
|
|
|
const renderFailure = async (id, render) => {
|
|
const registry = new TabRegistry();
|
|
const content = document.createElement('div');
|
|
const tabBar = document.createElement('div');
|
|
registry.register({ id, label: id, icon: '!', order: 10, render });
|
|
const manager = new TabManager(registry, content, tabBar);
|
|
manager.init();
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
const pane = content.querySelector(`#doctor-tab-${id}`);
|
|
return {
|
|
text: pane.textContent,
|
|
parsedMarker: pane.querySelector('#dynamic-status-tab-marker') !== null,
|
|
};
|
|
};
|
|
|
|
return {
|
|
payload,
|
|
sync: await renderFailure('dynamic-status-sync', () => { throw new Error(payload); }),
|
|
async: await renderFailure('dynamic-status-async', async () => { throw new Error(payload); }),
|
|
};
|
|
});
|
|
|
|
expect(result.sync.text).toContain(result.payload);
|
|
expect(result.sync.parsedMarker).toBe(false);
|
|
expect(result.async.text).toContain(result.payload);
|
|
expect(result.async.parsedMarker).toBe(false);
|
|
});
|
|
|
|
test('plain dynamic status keeps key-store load errors and provider IDs literal', async ({ page }) => {
|
|
const errorPayload = '<span id="dynamic-status-load-marker">load marker</span>';
|
|
const providerPayload = '<span id="dynamic-status-provider-marker">provider marker</span>';
|
|
let requestCount = 0;
|
|
await page.route('**/doctor/secrets/status', route => {
|
|
requestCount += 1;
|
|
const body = requestCount === 1
|
|
? { success: false, error: errorPayload }
|
|
: { success: true, providers: { [providerPayload]: { source: 'none' } } };
|
|
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(body) });
|
|
});
|
|
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
const grid = page.locator('#doctor-keystore-providers-grid');
|
|
await expect(grid).toContainText(errorPayload);
|
|
await expect(grid.locator('#dynamic-status-load-marker')).toHaveCount(0);
|
|
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await expect(grid).toContainText(providerPayload);
|
|
await expect(grid.locator('#dynamic-status-provider-marker')).toHaveCount(0);
|
|
});
|
|
|
|
test('plain dynamic status keeps caught key-store load exceptions literal', async ({ page }) => {
|
|
const payload = '<span id="dynamic-status-load-catch-marker">load catch marker</span>';
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.evaluate(async errorText => {
|
|
const { DoctorAPI } = await import('/web/doctor_api.js');
|
|
DoctorAPI.getSecretsStatus = async () => { throw new Error(errorText); };
|
|
}, payload);
|
|
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
const grid = page.locator('#doctor-keystore-providers-grid');
|
|
await expect(grid).toContainText(payload);
|
|
await expect(grid.locator('#dynamic-status-load-catch-marker')).toHaveCount(0);
|
|
});
|
|
|
|
test('plain dynamic status keeps key-store save result errors literal', async ({ page }) => {
|
|
const payload = '<span id="dynamic-status-save-marker">save marker</span>';
|
|
await page.route('**/doctor/secrets/status', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true, providers: {} }),
|
|
}));
|
|
await page.route('**/doctor/secrets', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: false, error: payload }),
|
|
}));
|
|
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await page.locator('#doctor-keystore-key').fill('fixture-value');
|
|
await page.locator('#doctor-keystore-save-btn').click();
|
|
|
|
const status = page.locator('#doctor-keystore-status');
|
|
await expect(status).toContainText(payload);
|
|
await expect(status.locator('#dynamic-status-save-marker')).toHaveCount(0);
|
|
await expect(page.locator('#doctor-keystore-save-btn')).toBeEnabled();
|
|
});
|
|
|
|
test('plain dynamic status keeps caught key-store save exceptions literal', async ({ page }) => {
|
|
const payload = '<span id="dynamic-status-save-catch-marker">save catch marker</span>';
|
|
await page.route('**/doctor/secrets/status', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true, providers: {} }),
|
|
}));
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await page.evaluate(async errorText => {
|
|
const { DoctorAPI } = await import('/web/doctor_api.js');
|
|
DoctorAPI.saveSecret = async () => { throw new Error(errorText); };
|
|
}, payload);
|
|
await page.locator('#doctor-keystore-key').fill('fixture-value');
|
|
await page.locator('#doctor-keystore-save-btn').click();
|
|
|
|
const status = page.locator('#doctor-keystore-status');
|
|
await expect(status).toContainText(payload);
|
|
await expect(status.locator('#dynamic-status-save-catch-marker')).toHaveCount(0);
|
|
await expect(page.locator('#doctor-keystore-save-btn')).toBeEnabled();
|
|
});
|
|
|
|
test('plain dynamic status keeps key-store delete result errors literal', async ({ page }) => {
|
|
const payload = '<span id="dynamic-status-delete-marker">delete marker</span>';
|
|
await page.route('**/doctor/secrets/*', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: false, error: payload }),
|
|
}));
|
|
await page.route('**/doctor/secrets/status', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true, providers: {} }),
|
|
}));
|
|
page.on('dialog', dialog => dialog.accept());
|
|
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await page.locator('#doctor-keystore-delete-btn').click();
|
|
|
|
const status = page.locator('#doctor-keystore-status');
|
|
await expect(status).toContainText(payload);
|
|
await expect(status.locator('#dynamic-status-delete-marker')).toHaveCount(0);
|
|
await expect(page.locator('#doctor-keystore-delete-btn')).toBeEnabled();
|
|
});
|
|
|
|
test('plain dynamic status keeps caught key-store delete exceptions literal', async ({ page }) => {
|
|
const payload = '<span id="dynamic-status-delete-catch-marker">delete catch marker</span>';
|
|
await page.route('**/doctor/secrets/status', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true, providers: {} }),
|
|
}));
|
|
page.on('dialog', dialog => dialog.accept());
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
await page.evaluate(async errorText => {
|
|
const { DoctorAPI } = await import('/web/doctor_api.js');
|
|
DoctorAPI.clearSecret = async () => { throw new Error(errorText); };
|
|
}, payload);
|
|
await page.locator('#doctor-keystore-delete-btn').click();
|
|
|
|
const status = page.locator('#doctor-keystore-status');
|
|
await expect(status).toContainText(payload);
|
|
await expect(status.locator('#dynamic-status-delete-catch-marker')).toHaveCount(0);
|
|
await expect(page.locator('#doctor-keystore-delete-btn')).toBeEnabled();
|
|
});
|
|
|
|
test('plain dynamic status preserves key-store validation and success feedback', async ({ page }) => {
|
|
await page.route('**/doctor/secrets/*', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true }),
|
|
}));
|
|
await page.route('**/doctor/secrets/status', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true, providers: { openai: { source: 'env' } } }),
|
|
}));
|
|
await page.route('**/doctor/secrets', route => route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ success: true }),
|
|
}));
|
|
page.on('dialog', dialog => dialog.accept());
|
|
|
|
await page.click('.doctor-tab-button[data-tab-id="settings"]');
|
|
await page.evaluate(() => {
|
|
const doctorUI = window.app.Doctor;
|
|
const originalGetUIText = doctorUI.getUIText.bind(doctorUI);
|
|
const keyStoreText = {
|
|
keystore_status_required: 'Provider and API Key are required.',
|
|
keystore_save_success: 'Saved {0} key to server.',
|
|
keystore_delete_success: 'Deleted {0} key.',
|
|
};
|
|
doctorUI.getUIText = key => keyStoreText[key] || originalGetUIText(key);
|
|
});
|
|
await page.locator('#doctor-key-store-section summary').click();
|
|
const status = page.locator('#doctor-keystore-status');
|
|
const grid = page.locator('#doctor-keystore-providers-grid');
|
|
await expect(grid).toContainText('openai');
|
|
await expect(grid).toContainText('ENV');
|
|
|
|
await page.locator('#doctor-keystore-save-btn').click();
|
|
await expect(status).toContainText('required');
|
|
await expect(status.locator('span')).toHaveCSS('color', 'rgb(240, 173, 78)');
|
|
|
|
await page.locator('#doctor-keystore-key').fill('fixture-value');
|
|
await page.locator('#doctor-keystore-save-btn').click();
|
|
await expect(status).toContainText('openai');
|
|
await expect(status.locator('span')).toHaveCSS('color', 'rgb(76, 175, 80)');
|
|
await expect(page.locator('#doctor-keystore-save-btn')).toBeEnabled();
|
|
|
|
await page.locator('#doctor-keystore-delete-btn').click();
|
|
await expect(status).toContainText('openai');
|
|
await expect(status.locator('span')).toHaveCSS('color', 'rgb(76, 175, 80)');
|
|
await expect(page.locator('#doctor-keystore-delete-btn')).toBeEnabled();
|
|
});
|
|
});
|