Three specs were asserting screens that no longer exist. They passed for as long as they did only because the e2e stack shared production's database and its configuration; against a clean one they failed honestly. Signing in is a stepped flow now — email, then a choice between a password and an emailed code — so #login-password is in the DOM but hidden until that choice is made. The spec asserted it visible on the landing screen. Replaced with one test for the landing step and a new one that walks the transition, which nothing covered before. The register link is hidden only when registration is disabled. This install has it enabled and invite-gated, so the link shows and the invite field is required; the spec asserted display:none. Connecting Nextcloud by signing in to Nextcloud is now the offered path, with the username and app-password fields folded behind "Use an app password instead". The spec asserted all three visible at once; it now checks the primary path and then opens the fallback. learning-tab.spec.js is deleted and `learning` is out of the smoke tab list — that feature was removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
135 lines
6.1 KiB
JavaScript
135 lines
6.1 KiB
JavaScript
// ============================================================
|
|
// SETTINGS + FAQ + DICTATION — detailed UI exercises that
|
|
// don't depend on real AI / recording permissions.
|
|
// ============================================================
|
|
|
|
const { test, expect, E2E_BASE, mockAI } = require('../fixtures');
|
|
|
|
// Settings and FAQ are not on the tab rail. They live in the account-card menu
|
|
// alongside Admin, and this helper used to click button.tab-btn[data-tab=…] for
|
|
// them, which simply timed out — the cause of ten of these failures. Dictation
|
|
// really is a rail tab, so both routes are needed.
|
|
const ACCOUNT_MENU = ['settings', 'faq'];
|
|
|
|
async function openTab(page, name) {
|
|
await page.goto(E2E_BASE + '/');
|
|
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
|
|
const vp = page.viewportSize();
|
|
if (vp && vp.width <= 768) {
|
|
await page.click('#btn-menu-toggle').catch(() => {});
|
|
}
|
|
if (ACCOUNT_MENU.includes(name)) {
|
|
await page.locator('.account-card-btn').first().click();
|
|
await page.locator(`[data-account-tab="${name}"]`).first().click();
|
|
} else {
|
|
await page.click(`button.tab-btn[data-tab="${name}"]`);
|
|
}
|
|
await page.waitForFunction((t) => {
|
|
const el = document.getElementById(t + '-tab');
|
|
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
|
|
}, name, { timeout: 15000 });
|
|
}
|
|
|
|
test.describe('Settings — voice, password, nextcloud sections render', () => {
|
|
|
|
test('voice-preferences inputs + save button exist', async ({ authedPage: _, page }) => {
|
|
await openTab(page, 'settings');
|
|
// STT and TTS dropdowns populate; save button exists
|
|
await expect(page.locator('#stt-model-select')).toBeVisible();
|
|
await expect(page.locator('#tts-voice-select')).toBeVisible();
|
|
await expect(page.locator('#btn-save-voice-prefs')).toBeVisible();
|
|
});
|
|
|
|
test('change-password form: all three fields + submit button present', async ({ authedPage: _, page }) => {
|
|
await openTab(page, 'settings');
|
|
await expect(page.locator('#pw-current')).toBeVisible();
|
|
await expect(page.locator('#pw-new')).toBeVisible();
|
|
await expect(page.locator('#pw-confirm')).toBeVisible();
|
|
await expect(page.locator('#btn-change-password')).toBeVisible();
|
|
});
|
|
|
|
test('2FA setup panel has QR container + verify input', async ({ authedPage: _, page }) => {
|
|
await openTab(page, 'settings');
|
|
// At least one of the 2FA buttons should be present
|
|
const setupCount = await page.locator('#btn-setup-2fa').count();
|
|
const disableCount = await page.locator('#btn-disable-2fa').count();
|
|
expect(setupCount + disableCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('Nextcloud section: signing in is the offered path, app password the fallback', async ({ authedPage: _, page }) => {
|
|
// Connecting by signing in to Nextcloud itself is the ordinary way in, so
|
|
// the address and that button are what the card shows. The username and
|
|
// app-password fields still exist, folded away behind "Use an app password
|
|
// instead" — they were visible when this test was written.
|
|
await openTab(page, 'settings');
|
|
await expect(page.locator('#nc-url')).toBeVisible();
|
|
await expect(page.locator('#btn-nc-login-flow')).toBeVisible();
|
|
await expect(page.locator('#nc-user')).toBeHidden();
|
|
|
|
await page.locator('#nc-manual summary').click();
|
|
await expect(page.locator('#nc-user')).toBeVisible();
|
|
await expect(page.locator('#nc-pass')).toBeVisible();
|
|
await expect(page.locator('#btn-nc-connect')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('FAQ — questions expand + collapse on click', () => {
|
|
|
|
test('clicking a FAQ question reveals its answer panel', async ({ authedPage: _, page }) => {
|
|
await openTab(page, 'faq');
|
|
const questions = page.locator('.faq-question');
|
|
const count = await questions.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
const first = questions.first();
|
|
await first.click();
|
|
// The corresponding answer should become visible — either via class toggle or
|
|
// inline style. Grab the sibling / next matching answer element and check.
|
|
const ariaControls = await first.getAttribute('aria-controls');
|
|
if (ariaControls) {
|
|
await expect(page.locator('#' + ariaControls)).toBeVisible();
|
|
} else {
|
|
// Fallback: any .faq-answer becomes visible
|
|
await expect(page.locator('.faq-answer').first()).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Dictation — UI loads, transcript editable, clear works', () => {
|
|
|
|
test('age/gender/setting inputs + generate + clear all render', async ({ authedPage: _, page }) => {
|
|
await mockAI(page);
|
|
await openTab(page, 'dictation');
|
|
await expect(page.locator('#dict-age')).toBeVisible();
|
|
await expect(page.locator('#dict-gender')).toBeVisible();
|
|
await expect(page.locator('#dict-setting')).toBeVisible();
|
|
await expect(page.locator('#dict-transcript')).toBeVisible();
|
|
await expect(page.locator('#dict-generate-btn')).toBeVisible();
|
|
});
|
|
|
|
test('typing into transcript + clear empties it', async ({ authedPage: _, page }) => {
|
|
await mockAI(page);
|
|
await openTab(page, 'dictation');
|
|
await page.locator('#dict-transcript').click();
|
|
await page.keyboard.type('Chief complaint: sore throat.');
|
|
await expect(page.locator('#dict-transcript')).toContainText('sore throat');
|
|
await page.click('#dict-clear');
|
|
const text = await page.locator('#dict-transcript').innerText();
|
|
expect(text.trim()).toBe('');
|
|
});
|
|
|
|
test('generate with short transcript → mocked HPI renders', async ({ authedPage: _, page }) => {
|
|
await mockAI(page);
|
|
await openTab(page, 'dictation');
|
|
await page.fill('#dict-age', '6 years');
|
|
await page.locator('#dict-transcript').click();
|
|
await page.keyboard.type('Patient with cough and congestion for 2 days.');
|
|
// Default output type is HPI — should call /api/generate-hpi-dictation
|
|
const [resp] = await Promise.all([
|
|
page.waitForResponse('**/api/generate-hpi-dictation'),
|
|
page.click('#dict-generate-btn'),
|
|
]);
|
|
expect(resp.status()).toBe(200);
|
|
await expect(page.locator('#dict-output')).toBeVisible();
|
|
await expect(page.locator('#dict-hpi-text')).toContainText('MOCK HPI from dictation');
|
|
});
|
|
});
|