blob: e7072a7a62c7430c207fed56b3231cdd69fd1e8e [file]
// The launch form and the session it starts: the form offers the profiles and the
// harnesses with none chosen, previews the command line it will run, says what this host
// has for each harness, and a started session is a terminal that streams, takes input,
// and is listed on the front page.
import { test, expect, sessionInfo } from './support/fixtures';
test('there is no default harness, and the preview follows the form', async ({ signedIn: page }) => {
const start = page.getByRole('button', { name: 'Start session' });
await expect(start).toBeDisabled();
await expect(page.getByText('Choose a profile or a harness first')).toBeVisible();
await page.locator('#harness').selectOption('codex');
await expect(start).toBeEnabled();
await page.locator('#auth input[value="key"]').check();
await page.locator('#provider').selectOption('openai');
await page.locator('#model').fill('gpt-test');
await expect(page.locator('#cmd')).toHaveText('amem launch --harness codex --auth key --provider openai --model gpt-test');
// a login is the harness's own: no destination to pick
await page.locator('#auth input[value="subscription"]').check();
await expect(page.locator('#provider')).toBeDisabled();
// task mode takes no prompt
await page.getByLabel('Work the next task from the ledger').check();
await expect(page.getByLabel('Prompt')).toBeDisabled();
await expect(page.locator('#cmd')).toContainText('amem work --harness codex');
});
test('a key with no credential behind it lists no models, and says why', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.locator('#auth input[value="key"]').check();
await expect(page.locator('#models-hint')).toContainText('No model list');
});
test('a profile fills the form, and the preview is the profile plus what was changed', async ({ signedIn: page }) => {
await expect(page.locator('#profile option[value="codex"]')).toHaveCount(1);
await page.locator('#profile').selectOption('codex');
await expect(page.locator('#harness')).toHaveValue('codex');
await expect(page.locator('#cmd')).toHaveText('amem codex');
await expect(page.getByRole('button', { name: 'Start session' })).toBeEnabled();
// the fields stay editable, and a change is a flag on top of the profile
await page.locator('#model').fill('gpt-test');
await expect(page.locator('#cmd')).toHaveText('amem codex --model gpt-test');
await page.getByLabel('Work the next task from the ledger').check();
await expect(page.locator('#cmd')).toHaveText('amem work codex --model gpt-test');
// back to none: the whole command, spelled out
await page.locator('#profile').selectOption('');
await expect(page.locator('#cmd')).toContainText('amem work --harness codex');
});
test('the backend picked is the backend the session is started with', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.locator('#backend').selectOption('namespaces');
await expect(page.locator('#cmd')).toContainText('--backend namespaces');
await page.getByRole('button', { name: 'Start session' }).click();
await expect(page).toHaveURL(/\/session\.html\?id=[0-9a-f]{32}$/);
await expect(page.locator('#status')).toHaveText('running');
const info = await sessionInfo(page);
expect(info.launch.backend).toBe('namespaces');
expect(info.launch.harness).toBe('claude');
});
test('each harness says what this host has for it, before anything starts', async ({ signedIn: page }) => {
// the server under test has no harness installed and no login or key: the doctor's
// words for what is missing, never a blank
const claude = page.locator('#ready li[data-harness="claude"]');
await expect(claude).toContainText(/not installed|no login/);
await expect(page.locator('#ready li')).toHaveCount(10);
await page.locator('#harness').selectOption('claude');
await expect(claude).toHaveClass(/is-open/);
await expect(page.locator('#auth-ready')).toContainText(/With subscription: (not installed|no login)/);
// the auth choices carry the verdict in their names, not only in a glyph
await expect(page.getByRole('radio', { name: /^Key, (not installed|no ANTHROPIC_API_KEY)$/ })).toHaveCount(1);
await expect(page.locator('#form-hint')).toContainText('may stop at once');
// asking the gateway is a separate, slower question
await page.getByRole('button', { name: 'Ask the gateway' }).click();
await expect(page.locator('#host-hint')).toContainText('Asked the gateway at', { timeout: 30_000 });
});
test('a started session streams, echoes what is typed, and is listed', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.getByLabel('Prompt').fill('summarise the record');
await page.getByRole('button', { name: 'Start session' }).click();
await expect(page).toHaveURL(/\/session\.html\?id=[0-9a-f]{32}$/);
await expect(page.locator('#status')).toHaveText('running');
await expect(page.locator('.xterm-rows')).toContainText('e2e session: ready');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('claude: summarise the record');
await page.locator('.xterm').click();
await page.keyboard.type('hello from the browser');
await page.keyboard.press('Enter');
await expect(page.locator('.xterm-rows')).toContainText('hello from the browser');
// a reload lands in the same place: the scrollback is the server's
await page.reload();
await expect(page.locator('.xterm-rows')).toContainText('hello from the browser');
const id = new URL(page.url()).searchParams.get('id');
await page.goto('/');
const row = page.locator(`a[href="session.html?id=${id}"]`);
await expect(row).toContainText('claude: summarise the record');
await expect(row).toContainText('running for');
await expect(row.getByRole('img', { name: 'running' })).toBeVisible();
});
test('the legacy address of a session opens the same page', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.getByRole('button', { name: 'Start session' }).click();
await expect(page.locator('.xterm-rows')).toContainText('e2e session: ready');
const id = new URL(page.url()).searchParams.get('id');
await page.goto(`/s/${id}`);
await expect(page).toHaveURL(new RegExp(`/session\\.html\\?id=${id}$`));
await expect(page.locator('.xterm-rows')).toContainText('e2e session: ready');
});
test('a killed session says it exited', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.getByRole('button', { name: 'Start session' }).click();
await expect(page.locator('.xterm-rows')).toContainText('e2e session: ready');
const id = new URL(page.url()).searchParams.get('id');
// the page's own key, through the page's own client
const status = await page.evaluate(
async (sid) => (await (window as unknown as { amem: { fetch: (p: string, o: object) => Promise<Response> } })
.amem.fetch(`/api/sessions/${sid}`, { method: 'DELETE' })).status,
id,
);
expect(status).toBe(204);
await expect(page.locator('#status')).toHaveText(/^exited/);
await page.goto('/');
await expect(page.locator(`a[href="session.html?id=${id}"]`)).toContainText('exited');
});
test('the preview quotes what a shell would read as a redirect', async ({ signedIn: page }) => {
await page.locator('#harness').selectOption('claude');
await page.locator('#prompt').fill('fix>notes.md');
await expect(page.locator('#cmd')).toContainText("--prompt 'fix>notes.md'");
});