Files
feuerwerk/tools/e2e/45-login.mjs
T

165 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { e2ePassword, E2E_USER } from '../lib/auth.mjs';
/**
* Signing in and out (#177), driven through the real screen: the rest of the suite gets its
* session handed to the browser as a cookie, so this is the only step that sees the sign-in
* screen at all.
*
* It runs last and hands the editor back the way it found it: signed in, with the fixture open.
*/
export async function login(t) {
const {
check,
cdp,
sleep,
evaluate,
json,
locate,
find,
press,
typeText,
mouse,
shot,
baseUrl,
projectId,
} = t;
const password = await e2ePassword();
/** Loads the page without handing the browser a session first. */
const openSignedOut = async () => {
await cdp.send('Network.clearBrowserCookies');
await cdp.send('Page.navigate', { url: `${baseUrl}?project=${projectId}` });
await sleep(3000);
};
/**
* Waits for the page to be showing one of the two screens. Signing in and out each mean a round
* trip to the server – deadlines guessed for that are how a check becomes a coin toss.
*/
const showing =
// The editor counts as up once the project is in it, not when its hook appears: the hook is
// installed before the project is fetched, and in a chained run that fetch is long enough for
// a check in between to read the empty project the editor starts out with.
{
editor: `__gm?.state?.project?.id === ${JSON.stringify(projectId)}`,
login: 'Boolean(window.__gm?.login)',
};
const waitForScreen = async (which) => {
for (let waited = 0; waited < 15000; waited += 250) {
try {
if (await evaluate(`Boolean(${showing[which]})`)) return true;
} catch {
// "Inspected target navigated or closed": only `openSignedOut` navigates here, and asking
// across it is not a reason to give up.
}
await sleep(250);
}
return false;
};
await openSignedOut();
const screenHook = await json(
'({ login: Boolean(window.__gm?.login), editor: Boolean(window.__gm?.state) })',
);
check(
'without a session the sign-in screen stands where the editor would',
screenHook.login && !screenHook.editor && Boolean(await find('Sign in')),
screenHook,
);
// The user name alone: refused by the form itself, before anything is sent.
await typeText(E2E_USER);
await press('Enter');
await press('Enter');
check(
'an empty password is refused without asking the server',
(await json('__gm.login()')).message === 'Enter your password',
await json('__gm.login()'),
);
await typeText('not-the-password');
await press('Enter');
await sleep(600);
// What the message says about a *wrong* password is checked in the unit tests: the live server
// sits behind a firewall that answers every non-2xx with its own 418 page, so the wording of the
// API's 401 only survives where nothing sits in between. That it is refused at all, and that
// nobody gets in, holds everywhere – and that is what this check is about.
check(
'a wrong password is refused and leaves the screen standing',
(await json('__gm.login()')).message !== '' &&
Boolean(await find('Sign in')) &&
!(await evaluate('Boolean(window.__gm?.state)')),
await json('__gm.login()'),
);
// Select-all first: the failed attempt left its text in the field.
await press('a', { ctrl: true });
await typeText(password);
const bullets = '•'.repeat(password.length);
check(
'the password is drawn as bullets, never as itself',
Boolean(await find(bullets)) && (await find(password)) === null,
{ bullets: bullets.length },
);
await shot('45-login');
await press('Enter');
await waitForScreen('editor');
const loaded = await json(
'({ editor: Boolean(window.__gm?.state), project: __gm?.state?.project?.name ?? null, title: document.title })',
);
check(
'the right pair opens the editor, with the account in the window title',
loaded.editor && loaded.project === 'E2E fixture' && loaded.title.endsWith(E2E_USER),
loaded,
);
// A mark on the window and on the canvas element: both survive a teardown and neither survives a
// reload, which is what signing out must not do (#177).
await evaluate("window.__stayedUp = 'mark'; document.querySelector('canvas').dataset.stayedUp = 'mark'");
// Pressed by hand, not with `clickText`: that one waits for a drawn frame afterwards, and the
// screen the button brings up is built before that frame is due.
const logout = await locate('Logout');
await mouse('mouseMoved', logout.x, logout.y);
await mouse('mousePressed', logout.x, logout.y);
await mouse('mouseReleased', logout.x, logout.y);
await waitForScreen('login');
check(
'Logout ends the session and comes back to the sign-in screen',
Boolean(await find('Sign in')) && !(await evaluate('Boolean(window.__gm?.state)')),
await json('({ login: Boolean(window.__gm?.login), editor: Boolean(window.__gm?.state) })'),
);
const stayedUp = await json(
"({ window: window.__stayedUp ?? null, canvas: document.querySelector('canvas').dataset.stayedUp ?? null })",
);
check(
'signing out does not reload the page: the same canvas stays, only what it draws changes',
stayedUp.window === 'mark' && stayedUp.canvas === 'mark',
stayedUp,
);
// Back in, so the suite ends on a signed-in editor like every other step leaves it.
await locate('Sign in');
await typeText(E2E_USER);
await press('Enter');
await typeText(password);
await press('Enter');
await waitForScreen('editor');
check(
'signing in again brings the editor back, with the project in it',
(await json('__gm?.state?.project?.name ?? null')) === 'E2E fixture',
await json('__gm?.state?.project?.name ?? null'),
);
// The rebuilt editor has to be laid out, not sitting unlaid-out in the corner: `resize` lays out
// only when the size changed, and the sign-in screen has set `root` to the size of the window
// already (see `App.ts`). The Logout button is the far right of the toolbar, so it says so.
const placed = await json('({ logout: __gm.locate("Logout")?.x ?? null, width: window.innerWidth })');
check(
'the editor that came back is laid out across the window',
placed.logout !== null && placed.logout > placed.width / 2,
placed,
);
}