- Introduced live smoke tests for Edge Gateways, verifying gateway routes and destructive-action prevention. - Added e2e scenarios for deep-link navigation, unavailable gateway recovery, token rotation, and background page refresh. - Refactored test helpers for streamlined functional validation in gateway scenarios. - Updated `EdgeGatewayTerminal.vue` with test IDs for enhanced testability. - Added and configured Husky pre-commit hooks for automated test file formatting and validation.
171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { mockApi } from "./support/network.js";
|
|
|
|
const USER_PASSKEY_BUTTON = "#passkey-login-button";
|
|
const SUBUSER_PASSKEY_BUTTON = "#subuser-passkey-login-button";
|
|
|
|
async function prepareUserPasskeyPage(page) {
|
|
await mockApi(page, { authenticated: false });
|
|
await page.goto("/login");
|
|
}
|
|
|
|
async function prepareSubuserPasskeyPage(page) {
|
|
await mockApi(page, { authenticated: false });
|
|
await page.goto("/login/driver");
|
|
}
|
|
|
|
async function stubPendingPasskeyFlow(page) {
|
|
await page.route(/\/auth\/passkey\/challenge$/, async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
data: {
|
|
challenge_token: "passkey-challenge-token",
|
|
publicKey: {
|
|
challenge: "AA",
|
|
timeout: 60000,
|
|
userVerification: "preferred",
|
|
allowCredentials: [],
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.evaluate(() => {
|
|
const existingCredentials = navigator.credentials || {};
|
|
Object.defineProperty(navigator, "credentials", {
|
|
configurable: true,
|
|
value: {
|
|
...existingCredentials,
|
|
get: () => new Promise(() => {}),
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// User Passkey Tests
|
|
test.describe("[AUTH][Passkey][User]", () => {
|
|
test("should display passkey login button on user login page when supported", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
const passkeyButton = page.locator(USER_PASSKEY_BUTTON);
|
|
const isVisible = await passkeyButton.isVisible();
|
|
|
|
if (isVisible) {
|
|
await expect(passkeyButton).toBeVisible();
|
|
await expect(passkeyButton.locator(".fa-fingerprint")).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("should have passkey button with fingerprint icon", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
const passkeyButton = page.locator(USER_PASSKEY_BUTTON);
|
|
|
|
if (!(await passkeyButton.isVisible())) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
await expect(passkeyButton.locator(".fa-fingerprint")).toBeVisible();
|
|
});
|
|
|
|
test("should display loading spinner when passkey login is clicked", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
await stubPendingPasskeyFlow(page);
|
|
|
|
const passkeyButton = page.locator(USER_PASSKEY_BUTTON);
|
|
|
|
if (!(await passkeyButton.isVisible())) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
await passkeyButton.click();
|
|
await expect(passkeyButton.locator(".fa-spinner")).toBeVisible();
|
|
});
|
|
|
|
test("should not disable other login options when passkey is available", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
await expect(page.getByTestId("login-customer-number")).toBeVisible();
|
|
await expect(page.getByTestId("login-password")).toBeVisible();
|
|
await expect(page.getByTestId("login-submit")).toBeVisible();
|
|
await expect(page.getByTestId("login-submit")).not.toBeDisabled();
|
|
});
|
|
|
|
test("should show QR code login option alongside passkey", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
await expect(page.locator('a[href="/login/qr"]')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// Subuser Passkey Tests
|
|
test.describe("[AUTH][Passkey][Subuser]", () => {
|
|
test("should display passkey login button on subuser login page when supported", async ({ page }) => {
|
|
await prepareSubuserPasskeyPage(page);
|
|
|
|
const passkeyButton = page.locator(SUBUSER_PASSKEY_BUTTON);
|
|
const isVisible = await passkeyButton.isVisible();
|
|
|
|
if (isVisible) {
|
|
await expect(passkeyButton).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("should not disable phone login when passkey is available", async ({ page }) => {
|
|
await prepareSubuserPasskeyPage(page);
|
|
|
|
await expect(page.locator('input[name="phone"]')).toBeVisible();
|
|
await expect(page.locator('input[name="password"]')).toBeVisible();
|
|
await expect(page.locator("#subuser-login-button")).toBeVisible();
|
|
await expect(page.locator("#subuser-login-button")).not.toBeDisabled();
|
|
});
|
|
});
|
|
|
|
// Passkey Browser Support Tests
|
|
test.describe("[AUTH][Passkey][Browser Support]", () => {
|
|
test("should gracefully handle browsers without passkey support", async ({ page, browserName }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
const passkeyButton = page.locator(USER_PASSKEY_BUTTON);
|
|
const isVisible = await passkeyButton.isVisible();
|
|
|
|
console.log(`Passkey button visible in ${browserName}: ${isVisible}`);
|
|
|
|
await expect(page.locator("form")).toBeVisible();
|
|
});
|
|
|
|
test("should always show password-based login as fallback", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
|
|
await expect(page.getByTestId("login-customer-number")).toBeVisible();
|
|
await expect(page.getByTestId("login-password")).toBeVisible();
|
|
await expect(page.getByTestId("login-submit")).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// Passkey Button State Tests
|
|
test.describe("[AUTH][Passkey][Button State]", () => {
|
|
test("should disable passkey button while loading", async ({ page }) => {
|
|
await prepareUserPasskeyPage(page);
|
|
await stubPendingPasskeyFlow(page);
|
|
|
|
const passkeyButton = page.locator(USER_PASSKEY_BUTTON);
|
|
|
|
if (!(await passkeyButton.isVisible())) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
await expect(passkeyButton).not.toBeDisabled();
|
|
|
|
await passkeyButton.click();
|
|
|
|
await expect(passkeyButton).toBeDisabled();
|
|
});
|
|
});
|