Add Playwright E2E suite against the built dist/

Pin @playwright/test 1.62.0 (matches the cached chromium
revision) and serve dist/ via python3 http.server, so the
deployed artifact is what gets tested. Covers page load,
terminal commands, vim mode, achievements, and the nav menu.
Ignore node_modules, test-results, playwright-report, and the
phased-execution runtime scratch (.agents/phase-sessions,
pipeline.log).
This commit is contained in:
2026-09-18 17:23:30 -04:00
parent 7f4f0b4ef1
commit 3898dc3c5e
10 changed files with 682 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
// @ts-check
const { test, expect } = require('@playwright/test');
const { openTerminal, typeCommand } = require('./helpers');
// The vim simulator renders into a fixed full-screen overlay appended to
// <body>; text assertions below run against the whole body and pass/fail
// on the overlay's presence or absence.
test.describe('Fake terminal — vim simulator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await openTerminal(page);
});
test('vim with no filename shows the splash and q exits', async ({ page }) => {
await typeCommand(page, 'vim');
await expect(page.locator('body')).toContainText('VIM - Vi sIgnificantly worse M');
await expect(page.locator('body')).toContainText('version 0.0.0-alpha');
await page.keyboard.press('q');
await expect(page.locator('body')).not.toContainText('VIM - Vi sIgnificantly worse M');
// terminal is usable again
await typeCommand(page, 'hostname');
await expect(page.locator('.terminal-content > div').nth(-2)).toHaveText('homelab');
});
test('vim with a filename opens a buffer; insert, save & quit', async ({ page }) => {
await typeCommand(page, 'vim notes.txt');
await expect(page.locator('body')).toContainText('Welcome to notes.txt!');
await expect(page.locator('body')).toContainText('"notes.txt"');
// insert mode, type text at column 0
await page.keyboard.press('i');
await expect(page.locator('body')).toContainText('-- INSERT --');
await page.keyboard.type('hello');
await expect(page.locator('body')).toContainText('helloWelcome to notes.txt!');
// back to normal mode
await page.keyboard.press('Escape');
await expect(page.locator('body')).not.toContainText('-- INSERT --');
// :wq saves (simulated) and exits
await page.keyboard.type(':wq');
await page.keyboard.press('Enter');
await expect(page.locator('body')).not.toContainText('Welcome to notes.txt!');
await expect(page.locator('.terminal-content')).toContainText('File saved (simulated).');
});
test('vim normal-mode navigation and line deletion', async ({ page }) => {
await typeCommand(page, 'vim file.md');
await expect(page.locator('body')).toContainText('Welcome to file.md!');
// open a new line below (o), type, escape
await page.keyboard.press('o');
await expect(page.locator('body')).toContainText('-- INSERT --');
await page.keyboard.type('line two');
await page.keyboard.press('Escape');
// both lines present
await expect(page.locator('body')).toContainText('Welcome to file.md!');
await expect(page.locator('body')).toContainText('line two');
// G jumps to the last line, dd deletes it (Shift+D in this sim).
// Use innerText: deleted lines are hidden (display:none) but remain
// in the DOM, so textContent would still contain them.
await page.keyboard.press('G');
await page.keyboard.press('Shift+D');
await expect.poll(async () =>
page.evaluate(() => document.body.innerText)
).not.toContain('line two');
await expect(page.locator('body')).toContainText('Welcome to file.md!');
// :q! force-quits
await page.keyboard.type(':q!');
await page.keyboard.press('Enter');
await expect(page.locator('body')).not.toContainText('Welcome to file.md!');
});
});