124 lines
4.9 KiB
JavaScript
124 lines
4.9 KiB
JavaScript
/**
|
|
* Playwright acceptance test for the k_phone portal (Component 2, port 8771).
|
|
*
|
|
* Run:
|
|
* K_PHONE_BASE_URL=http://192.168.x.x:8771 npx playwright test tests/k_phone_portal.spec.js
|
|
*
|
|
* Env vars:
|
|
* K_PHONE_BASE_URL Base URL of the k_phone proxy service (default: http://127.0.0.1:8771)
|
|
* CARD_REGISTRATION_TIMEOUT_MS Timeout for makeCredential card step (default: 90000)
|
|
* CARD_LOGIN_TIMEOUT_MS Timeout for getAssertion card step (default: 90000)
|
|
* PW_HEADLESS Set to "1" for headless mode
|
|
*
|
|
* Constraint: the test does not read the Android log — all assertions are
|
|
* made against visible DOM state and the #log pre element.
|
|
*/
|
|
|
|
const { test, expect } = require("@playwright/test");
|
|
|
|
const BASE_URL = process.env.K_PHONE_BASE_URL || "http://127.0.0.1:8771";
|
|
const registrationTimeoutMs = Number(process.env.CARD_REGISTRATION_TIMEOUT_MS || "90000");
|
|
const loginTimeoutMs = Number(process.env.CARD_LOGIN_TIMEOUT_MS || "90000");
|
|
|
|
function uniqueUsername() {
|
|
return `pw_${Date.now().toString(36)}`;
|
|
}
|
|
|
|
async function waitForLog(page, expectedText, timeoutMs = 10_000) {
|
|
await expect(page.locator("#log")).toContainText(expectedText, { timeout: timeoutMs });
|
|
}
|
|
|
|
test.describe("k_phone portal regression", () => {
|
|
test(
|
|
"enrolls, logs in, checks session status, logs out, and deletes user",
|
|
async ({ page }) => {
|
|
const username = uniqueUsername();
|
|
|
|
test.setTimeout(registrationTimeoutMs + loginTimeoutMs + 60_000);
|
|
|
|
await page.goto(BASE_URL + "/");
|
|
await expect(
|
|
page.getByRole("heading", { name: "ChromeCard k_phone Portal" })
|
|
).toBeVisible();
|
|
|
|
// Clear any leftover localStorage from a previous session so the test
|
|
// starts from a clean slate regardless of browser profile state.
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload();
|
|
|
|
await test.step("Initial state is unauthenticated", async () => {
|
|
await expect(page.locator("#storedUser")).toHaveText("none");
|
|
await expect(page.locator("#sessionActive")).toHaveText("no");
|
|
});
|
|
|
|
await test.step("Enroll user", async () => {
|
|
await page.locator("#username").fill(username);
|
|
await page.locator("#displayName").fill("Playwright Test");
|
|
// Card step: makeCredential — touch user fingerprint on ChromeCard.
|
|
await page.locator("#enrollBtn").click();
|
|
await waitForLog(page, "Enrolled", registrationTimeoutMs);
|
|
await expect(page.locator("#storedUser")).toHaveText(username);
|
|
});
|
|
|
|
await test.step("Login", async () => {
|
|
// Card step: getAssertion — touch user fingerprint on ChromeCard.
|
|
await page.locator("#loginBtn").click();
|
|
await waitForLog(page, "Login ok", loginTimeoutMs);
|
|
await expect(page.locator("#sessionActive")).toHaveText("yes");
|
|
});
|
|
|
|
await test.step("Session status reflects active session", async () => {
|
|
await page.locator("#statusBtn").click();
|
|
await waitForLog(page, "Session status");
|
|
});
|
|
|
|
await test.step("List users includes enrolled user", async () => {
|
|
await page.locator("#listBtn").click();
|
|
await waitForLog(page, username);
|
|
});
|
|
|
|
await test.step("Logout clears session", async () => {
|
|
await page.locator("#logoutBtn").click();
|
|
// "Logout" is a substring of "Logout failed", so assert the semantic
|
|
// outcome (sessionActive → no) rather than the log message text.
|
|
await expect(page.locator("#sessionActive")).toHaveText("no", {
|
|
timeout: 10_000,
|
|
});
|
|
});
|
|
|
|
await test.step("Delete user clears stored identity", async () => {
|
|
await page.locator("#deleteBtn").click();
|
|
// "Deleted" is not a substring of "Delete failed" — safe to match.
|
|
await waitForLog(page, "Deleted");
|
|
await expect(page.locator("#storedUser")).toHaveText("none");
|
|
await expect(page.locator("#sessionActive")).toHaveText("no");
|
|
});
|
|
}
|
|
);
|
|
|
|
test("enrollment failure is surfaced in log", async ({ page }) => {
|
|
await page.goto(BASE_URL + "/");
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload();
|
|
|
|
// Submit enroll with an empty username — server must reject it.
|
|
await page.locator("#username").fill("");
|
|
await page.locator("#enrollBtn").click();
|
|
await waitForLog(page, "Enroll failed");
|
|
// No username must have been stored on failure.
|
|
await expect(page.locator("#storedUser")).toHaveText("none");
|
|
});
|
|
|
|
test("login without enrollment fails gracefully", async ({ page }) => {
|
|
await page.goto(BASE_URL + "/");
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.reload();
|
|
|
|
// Attempt login with a username that is not enrolled.
|
|
await page.locator("#username").fill("no_such_user_pw");
|
|
await page.locator("#loginBtn").click();
|
|
await waitForLog(page, "Login failed");
|
|
await expect(page.locator("#sessionActive")).toHaveText("no");
|
|
});
|
|
});
|