9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
452 lines
18 KiB
TypeScript
452 lines
18 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
import { mkdirSync } from "node:fs";
|
|
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
|
|
const SCREENSHOT_DIR = "test-results/mobile-pairing-qr";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await installMockBridge(page, { pairingStartDelayMs: 300 });
|
|
});
|
|
|
|
async function emitPairingEvent(page: Page, event: string, payload?: unknown) {
|
|
await page.evaluate(
|
|
async ({ eventName, eventPayload }) => {
|
|
const internals = (
|
|
window as Window & {
|
|
__TAURI_INTERNALS__?: {
|
|
invoke?: (
|
|
command: string,
|
|
args: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
};
|
|
}
|
|
).__TAURI_INTERNALS__;
|
|
if (!internals?.invoke) {
|
|
throw new Error("Tauri E2E event bridge is unavailable");
|
|
}
|
|
await internals.invoke("plugin:event|emit", {
|
|
event: eventName,
|
|
payload: eventPayload,
|
|
});
|
|
},
|
|
{ eventName: event, eventPayload: payload },
|
|
);
|
|
}
|
|
|
|
test("mobile pairing starts on demand and reveals the QR code", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("open-settings").click();
|
|
await page.getByTestId("profile-popover-settings").click();
|
|
await page.getByTestId("settings-nav-mobile").click();
|
|
|
|
mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
|
|
const section = page.getByTestId("settings-mobile");
|
|
const card = page.getByTestId("mobile-pairing-card");
|
|
const layout = card.getByTestId("mobile-pairing-layout");
|
|
const qrContainer = page.getByTestId("mobile-pairing-qr-container");
|
|
const steps = card.getByTestId("mobile-pairing-steps");
|
|
const scanStepIndicator = card.getByTestId(
|
|
"mobile-pairing-scan-step-indicator",
|
|
);
|
|
const confirmStepIndicator = card.getByTestId(
|
|
"mobile-pairing-confirm-step-indicator",
|
|
);
|
|
const finalStep = card.getByTestId("mobile-pairing-final-step");
|
|
const startButton = card.getByTestId("start-pairing-button");
|
|
await expect(card).toBeVisible();
|
|
await expect(startButton).toHaveText("Start pairing");
|
|
await expect(steps.getByText("Scan QR code", { exact: true })).toBeVisible();
|
|
await expect(
|
|
steps.getByText("Confirm mobile code", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
finalStep.getByText("Pair your mobile app", { exact: true }),
|
|
).toBeVisible();
|
|
const finalStepIndicator = finalStep.getByTestId(
|
|
"mobile-pairing-final-step-indicator",
|
|
);
|
|
await expect(finalStepIndicator).toHaveText("3");
|
|
await expect(scanStepIndicator).toHaveAttribute("data-completed", "false");
|
|
await expect(confirmStepIndicator).toHaveAttribute("data-completed", "false");
|
|
await expect(finalStepIndicator).toHaveCSS("width", "48px");
|
|
await expect(finalStepIndicator).toHaveCSS("height", "48px");
|
|
expect(
|
|
await finalStepIndicator.evaluate((element) =>
|
|
Number.parseFloat(getComputedStyle(element).borderRadius),
|
|
),
|
|
).toBeGreaterThanOrEqual(24);
|
|
const indicatorBackground = await finalStepIndicator.evaluate(
|
|
(element) => getComputedStyle(element).backgroundColor,
|
|
);
|
|
const primaryActionBackground = await startButton.evaluate(
|
|
(element) => getComputedStyle(element).backgroundColor,
|
|
);
|
|
expect(indicatorBackground).not.toBe(primaryActionBackground);
|
|
await expect(layout).toHaveCSS("padding-top", "60px");
|
|
await expect(layout).toHaveCSS("padding-right", "60px");
|
|
await expect(layout).toHaveCSS("padding-left", "60px");
|
|
await expect(layout).toHaveCSS("column-gap", "56px");
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toHaveCount(0);
|
|
await expect(page.getByTestId("copy-pairing-code")).toHaveCount(0);
|
|
expect(
|
|
await page.evaluate(
|
|
() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
|
(entry) => entry.command === "start_pairing",
|
|
).length,
|
|
),
|
|
).toBe(0);
|
|
|
|
const sectionBox = await section.boundingBox();
|
|
const cardBox = await card.boundingBox();
|
|
const initialStepsBox = await steps.boundingBox();
|
|
expect(sectionBox).not.toBeNull();
|
|
expect(cardBox).not.toBeNull();
|
|
expect(initialStepsBox).not.toBeNull();
|
|
const initialQrBox = await qrContainer.boundingBox();
|
|
expect(initialQrBox).not.toBeNull();
|
|
const qrTopSpace = (initialQrBox?.y ?? 0) - (cardBox?.y ?? 0);
|
|
const qrLeftSpace = (initialQrBox?.x ?? 0) - (cardBox?.x ?? 0);
|
|
const qrBottomSpace =
|
|
(cardBox?.y ?? 0) +
|
|
(cardBox?.height ?? 0) -
|
|
((initialQrBox?.y ?? 0) + (initialQrBox?.height ?? 0));
|
|
expect(Math.abs(qrTopSpace - qrLeftSpace)).toBeLessThan(0.5);
|
|
expect(Math.abs(qrTopSpace - qrBottomSpace)).toBeLessThan(0.5);
|
|
const sectionCenter = (sectionBox?.x ?? 0) + (sectionBox?.width ?? 0) / 2;
|
|
const cardCenter = (cardBox?.x ?? 0) + (cardBox?.width ?? 0) / 2;
|
|
expect(Math.abs(sectionCenter - cardCenter)).toBeLessThan(0.5);
|
|
await waitForAnimations(page);
|
|
await card.screenshot({ path: `${SCREENSHOT_DIR}/pairing-start.png` });
|
|
|
|
await startButton.click();
|
|
|
|
const loadingSpinner = card.getByTestId("pairing-loading-spinner");
|
|
await expect(loadingSpinner).toBeVisible();
|
|
await expect(loadingSpinner).toHaveCSS("animation-name", "spin");
|
|
|
|
const qrCode = page.getByTestId("mobile-pairing-qr");
|
|
const copyButton = card.getByTestId("copy-pairing-code");
|
|
await expect(qrCode).toBeVisible();
|
|
await expect(copyButton).toHaveText("Copy pairing code");
|
|
await expect(startButton).toHaveCount(0);
|
|
await expect(card.getByText("Pair mobile device")).toHaveCount(0);
|
|
await expect(page.getByTestId("mobile-pairing-dialog")).toHaveCount(0);
|
|
await expect(
|
|
page.getByText("Securely transfer your identity via NIP-AB protocol"),
|
|
).toHaveCount(0);
|
|
await expect(qrCode).toHaveAttribute("data-qr-matrix-size", "57");
|
|
await expect(qrCode.locator("[data-qr-finder-pattern]")).toHaveCount(3);
|
|
expect(await qrCode.locator("circle").count()).toBeGreaterThan(100);
|
|
expect(await qrCode.locator(".buzz-qr-cell-reveal").count()).toBeGreaterThan(
|
|
100,
|
|
);
|
|
await expect(qrCode.locator(".buzz-qr-cell-reveal").first()).toHaveCSS(
|
|
"animation-name",
|
|
"buzz-qr-cell-reveal",
|
|
);
|
|
await expect(qrCode.locator(".buzz-qr-cell-reveal").first()).toHaveCSS(
|
|
"animation-duration",
|
|
"0.058s",
|
|
);
|
|
await expect(qrCode.locator(".buzz-qr-cell-reveal").first()).toHaveCSS(
|
|
"animation-timing-function",
|
|
"linear",
|
|
);
|
|
await expect(
|
|
qrCode.locator('[data-qr-cell-row="56"].buzz-qr-cell-reveal').first(),
|
|
).toHaveCSS("animation-delay", "0.189s");
|
|
await expect(copyButton).toHaveCSS("animation-name", "enter");
|
|
await expect(copyButton).toHaveCSS("animation-duration", "0.25s");
|
|
await expect(qrCode.locator("image")).toHaveAttribute(
|
|
"href",
|
|
"/app-icon@2x.png",
|
|
);
|
|
await waitForAnimations(page);
|
|
const qrBox = await qrContainer.boundingBox();
|
|
const copyBox = await copyButton.boundingBox();
|
|
const stepsBox = await steps.boundingBox();
|
|
const qrCardBox = await card.boundingBox();
|
|
expect(qrBox).not.toBeNull();
|
|
expect(copyBox).not.toBeNull();
|
|
expect(stepsBox).not.toBeNull();
|
|
expect(qrCardBox).not.toBeNull();
|
|
expect(qrBox?.x ?? 0).toBeLessThan(stepsBox?.x ?? 0);
|
|
expect(Math.abs((stepsBox?.y ?? 0) - (initialStepsBox?.y ?? 0))).toBeLessThan(
|
|
0.5,
|
|
);
|
|
expect(
|
|
Math.abs((qrCardBox?.height ?? 0) - (cardBox?.height ?? 0)),
|
|
).toBeLessThan(0.5);
|
|
expect(copyBox?.y ?? 0).toBeGreaterThan(
|
|
(qrBox?.y ?? 0) + (qrBox?.height ?? 0),
|
|
);
|
|
expect(Math.abs((copyBox?.x ?? 0) - (qrBox?.x ?? 0))).toBeLessThan(0.5);
|
|
expect(Math.abs((copyBox?.width ?? 0) - (qrBox?.width ?? 0))).toBeLessThan(
|
|
0.5,
|
|
);
|
|
|
|
await emitPairingEvent(page, "pairing-error", {
|
|
message: "Session timed out",
|
|
});
|
|
|
|
await expect(qrCode).toHaveCount(0);
|
|
await expect(copyButton).toHaveCount(0);
|
|
await expect(card.getByText("Pairing code expired.")).toBeVisible();
|
|
|
|
const regenerateButton = card.getByTestId("regenerate-pairing-button");
|
|
await expect(regenerateButton).toHaveText("Generate new pairing code");
|
|
await waitForAnimations(page);
|
|
await card.screenshot({ path: `${SCREENSHOT_DIR}/pairing-expired.png` });
|
|
await regenerateButton.click();
|
|
await expect(loadingSpinner).toBeVisible();
|
|
await expect(qrCode).toBeVisible();
|
|
await expect(copyButton).toBeVisible();
|
|
expect(
|
|
await page.evaluate(
|
|
() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
|
(entry) => entry.command === "start_pairing",
|
|
).length,
|
|
),
|
|
).toBe(2);
|
|
|
|
await waitForAnimations(page);
|
|
await card.screenshot({ path: `${SCREENSHOT_DIR}/pairing-card.png` });
|
|
await qrCode.screenshot({ path: `${SCREENSHOT_DIR}/pairing-qr.png` });
|
|
});
|
|
|
|
test("pairing completion updates the final step and resets after leaving", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("open-settings").click();
|
|
await page.getByTestId("profile-popover-settings").click();
|
|
await page.getByTestId("settings-nav-mobile").click();
|
|
|
|
const card = page.getByTestId("mobile-pairing-card");
|
|
const finalStep = card.getByTestId("mobile-pairing-final-step");
|
|
const scanStepIndicator = card.getByTestId(
|
|
"mobile-pairing-scan-step-indicator",
|
|
);
|
|
const confirmStepIndicator = card.getByTestId(
|
|
"mobile-pairing-confirm-step-indicator",
|
|
);
|
|
await card.getByTestId("start-pairing-button").click();
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toBeVisible();
|
|
|
|
await emitPairingEvent(page, "pairing-sas-received", { sas: "123456" });
|
|
const confirmation = card.getByTestId("mobile-pairing-code-confirmation");
|
|
await expect(scanStepIndicator).toHaveAttribute("data-completed", "true");
|
|
await expect(scanStepIndicator.locator('[data-state="complete"]')).toHaveCSS(
|
|
"opacity",
|
|
"1",
|
|
);
|
|
await expect(scanStepIndicator.locator("svg")).toHaveCount(1);
|
|
await expect(confirmStepIndicator).toHaveAttribute("data-completed", "false");
|
|
await expect(page.getByTestId("mobile-pairing-dialog")).toHaveCount(0);
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toHaveCount(0);
|
|
const confirmationCode = confirmation.getByTestId("pairing-sas-code");
|
|
await expect(confirmationCode).toHaveAccessibleName(
|
|
"Confirmation code 123 456",
|
|
);
|
|
await expect(
|
|
confirmationCode.locator('[data-testid^="pairing-sas-code-digit-"]'),
|
|
).toHaveCount(6);
|
|
await expect(
|
|
confirmationCode.getByTestId("pairing-sas-code-digit-1"),
|
|
).toHaveCSS("border-radius", "12px");
|
|
await expect(
|
|
confirmationCode.getByTestId("pairing-sas-code-digit-1"),
|
|
).toHaveCSS("box-shadow", "none");
|
|
const firstCodeDigit = confirmationCode.getByTestId(
|
|
"pairing-sas-code-digit-1",
|
|
);
|
|
const firstCodeDigitBox = await firstCodeDigit.boundingBox();
|
|
expect(firstCodeDigitBox).not.toBeNull();
|
|
expect(firstCodeDigitBox?.width ?? 0).toBeGreaterThan(32);
|
|
expect(firstCodeDigitBox?.height ?? 0).toBeGreaterThan(48);
|
|
const secondCodeDigitBox = await confirmationCode
|
|
.getByTestId("pairing-sas-code-digit-2")
|
|
.boundingBox();
|
|
const thirdCodeDigitBox = await confirmationCode
|
|
.getByTestId("pairing-sas-code-digit-3")
|
|
.boundingBox();
|
|
const fourthCodeDigitBox = await confirmationCode
|
|
.getByTestId("pairing-sas-code-digit-4")
|
|
.boundingBox();
|
|
expect(secondCodeDigitBox).not.toBeNull();
|
|
expect(thirdCodeDigitBox).not.toBeNull();
|
|
expect(fourthCodeDigitBox).not.toBeNull();
|
|
const regularDigitGap =
|
|
(thirdCodeDigitBox?.x ?? 0) -
|
|
((secondCodeDigitBox?.x ?? 0) + (secondCodeDigitBox?.width ?? 0));
|
|
const groupedDigitGap =
|
|
(fourthCodeDigitBox?.x ?? 0) -
|
|
((thirdCodeDigitBox?.x ?? 0) + (thirdCodeDigitBox?.width ?? 0));
|
|
expect(groupedDigitGap).toBeGreaterThan(regularDigitGap);
|
|
await expect(card.getByTestId("mobile-pairing-qr-container")).toHaveCSS(
|
|
"border-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
const confirmButton = confirmation.getByTestId("confirm-sas");
|
|
const cancelButton = confirmation.getByTestId("deny-sas");
|
|
const confirmationBox = await confirmation.boundingBox();
|
|
const confirmationTitleBox = await confirmation
|
|
.getByTestId("pairing-sas-title")
|
|
.boundingBox();
|
|
const confirmationCodeBox = await confirmationCode.boundingBox();
|
|
const confirmationActionsBox = await confirmation
|
|
.getByTestId("pairing-sas-actions")
|
|
.boundingBox();
|
|
expect(confirmationBox).not.toBeNull();
|
|
expect(confirmationTitleBox).not.toBeNull();
|
|
expect(confirmationCodeBox).not.toBeNull();
|
|
expect(confirmationActionsBox).not.toBeNull();
|
|
expect(
|
|
Math.abs((confirmationTitleBox?.y ?? 0) - (confirmationBox?.y ?? 0)),
|
|
).toBeLessThan(0.5);
|
|
const codeCenter =
|
|
(confirmationCodeBox?.y ?? 0) + (confirmationCodeBox?.height ?? 0) / 2;
|
|
const availableCodeCenter =
|
|
((confirmationTitleBox?.y ?? 0) +
|
|
(confirmationTitleBox?.height ?? 0) +
|
|
(confirmationActionsBox?.y ?? 0)) /
|
|
2;
|
|
expect(Math.abs(availableCodeCenter - codeCenter)).toBeLessThan(0.5);
|
|
expect(
|
|
Math.abs(
|
|
(confirmationActionsBox?.y ?? 0) +
|
|
(confirmationActionsBox?.height ?? 0) -
|
|
((confirmationBox?.y ?? 0) + (confirmationBox?.height ?? 0)),
|
|
),
|
|
).toBeLessThan(0.5);
|
|
await expect(confirmButton).toHaveCSS("height", "36px");
|
|
await expect(cancelButton).toHaveCSS("height", "36px");
|
|
await expect(confirmButton.locator("svg")).toHaveCount(1);
|
|
await expect(cancelButton.locator("svg")).toHaveCount(0);
|
|
const confirmBox = await confirmButton.boundingBox();
|
|
const cancelBox = await cancelButton.boundingBox();
|
|
expect(confirmBox).not.toBeNull();
|
|
expect(cancelBox).not.toBeNull();
|
|
expect(confirmBox?.y ?? 0).toBeLessThan(cancelBox?.y ?? 0);
|
|
await expect(
|
|
confirmation.getByText(/Only confirm if you started this pairing/),
|
|
).toHaveCount(0);
|
|
await expect(confirmation.getByTestId("pairing-sas-title")).toHaveCSS(
|
|
"font-size",
|
|
"16px",
|
|
);
|
|
|
|
mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
await waitForAnimations(page);
|
|
await card.screenshot({
|
|
path: `${SCREENSHOT_DIR}/pairing-code-confirmation.png`,
|
|
});
|
|
|
|
await confirmation.getByTestId("confirm-sas").click();
|
|
await expect(card.getByText("Pairing mobile device...")).toBeVisible();
|
|
await expect(confirmStepIndicator).toHaveAttribute("data-completed", "true");
|
|
await expect(
|
|
confirmStepIndicator.locator('[data-state="complete"]'),
|
|
).toHaveCSS("opacity", "1");
|
|
await expect(confirmStepIndicator.locator("svg")).toHaveCount(1);
|
|
|
|
await emitPairingEvent(page, "pairing-complete");
|
|
|
|
await expect(finalStep.getByText("Paired", { exact: true })).toBeVisible();
|
|
await expect(
|
|
finalStep.getByText("Your mobile app is now connected to this relay."),
|
|
).toBeVisible();
|
|
await expect(
|
|
finalStep.getByTestId("mobile-pairing-final-step-indicator").locator("svg"),
|
|
).toHaveCount(1);
|
|
await expect(
|
|
finalStep.getByTestId("mobile-pairing-final-step-indicator"),
|
|
).toHaveAttribute("data-completed", "true");
|
|
const pairedSurface = card.getByTestId("mobile-pairing-qr-container");
|
|
await expect(
|
|
pairedSurface.getByText("Paired", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
pairedSurface.getByText("Your mobile app is now connected to this relay."),
|
|
).toHaveCount(0);
|
|
|
|
await waitForAnimations(page);
|
|
await card.screenshot({ path: `${SCREENSHOT_DIR}/pairing-complete.png` });
|
|
|
|
await page.getByTestId("settings-nav-updates").click();
|
|
await page.getByTestId("settings-nav-mobile").click();
|
|
|
|
const restartedCard = page.getByTestId("mobile-pairing-card");
|
|
await expect(restartedCard.getByTestId("start-pairing-button")).toBeVisible();
|
|
await expect(
|
|
restartedCard
|
|
.getByTestId("mobile-pairing-final-step")
|
|
.getByText("Pair your mobile app", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toHaveCount(0);
|
|
});
|
|
|
|
test("late pairing events are ignored after canceling", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByTestId("open-settings").click();
|
|
await page.getByTestId("profile-popover-settings").click();
|
|
await page.getByTestId("settings-nav-mobile").click();
|
|
|
|
const card = page.getByTestId("mobile-pairing-card");
|
|
await card.getByTestId("start-pairing-button").click();
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toBeVisible();
|
|
|
|
await emitPairingEvent(page, "pairing-sas-received", { sas: "123456" });
|
|
const confirmation = card.getByTestId("mobile-pairing-code-confirmation");
|
|
await expect(confirmation).toBeVisible();
|
|
await confirmation.getByTestId("deny-sas").click();
|
|
|
|
await expect(confirmation).toHaveCount(0);
|
|
await expect(
|
|
card.getByText("The codes didn't match. Pairing was canceled."),
|
|
).toBeVisible();
|
|
|
|
await emitPairingEvent(page, "pairing-complete");
|
|
await emitPairingEvent(page, "pairing-sas-received", { sas: "654321" });
|
|
|
|
await expect(confirmation).toHaveCount(0);
|
|
await expect(
|
|
card
|
|
.getByTestId("mobile-pairing-final-step")
|
|
.getByText("Paired", { exact: true }),
|
|
).toHaveCount(0);
|
|
await expect(
|
|
card.getByText("The codes didn't match. Pairing was canceled."),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("step completion respects reduced motion", async ({ page }) => {
|
|
await page.emulateMedia({ reducedMotion: "reduce" });
|
|
await page.goto("/");
|
|
await page.getByTestId("open-settings").click();
|
|
await page.getByTestId("profile-popover-settings").click();
|
|
await page.getByTestId("settings-nav-mobile").click();
|
|
|
|
const card = page.getByTestId("mobile-pairing-card");
|
|
const scanStepIndicator = card.getByTestId(
|
|
"mobile-pairing-scan-step-indicator",
|
|
);
|
|
await card.getByTestId("start-pairing-button").click();
|
|
await expect(page.getByTestId("mobile-pairing-qr")).toBeVisible();
|
|
|
|
await emitPairingEvent(page, "pairing-sas-received", { sas: "123456" });
|
|
|
|
await expect(scanStepIndicator).toHaveAttribute("data-completed", "true");
|
|
await expect(scanStepIndicator).toHaveCSS("transition-property", "none");
|
|
const completedContent = scanStepIndicator.locator('[data-state="complete"]');
|
|
await expect(completedContent).toHaveCSS("opacity", "1");
|
|
await expect(completedContent).toHaveCSS("transform", "none");
|
|
});
|