A tab names the account it was claimed for on every request, beacon and upload (X-Siftlode-Expect-Account); current_user and /auth/logout refuse a mismatch with 401 account_changed and keep the session, so a tab left showing the account signed in before reloads instead of writing to the new one. /auth/login clears a connect marker left by an abandoned connect, and a link completes only on a session that still holds. The prefs merge reads the row under a lock. A tab's sessionStorage records its own owner. A page the account loses is given up, address bar and remembered page included. The panel layouts are preferences; the storage contract also refuses a key handed to the Storage API or to usePref outside its kind. A refused sign-in as someone else and a failed sign-out say so. The chromium-only e2e specs are in the gate register, the member walk waits for its requests, and devices.spec checks the save itself.
62 lines
2.8 KiB
TypeScript
62 lines
2.8 KiB
TypeScript
import { test, expect, devices, type Browser, type Page } from "@playwright/test";
|
|
import { MEMBER_STORAGE, gotoPage, openApp } from "./helpers";
|
|
|
|
// One member on two devices, a desktop and an iPad, each with its own browser storage. A preference is
|
|
// a choice about the account, so a change on one device shows on the other; a device setting (here
|
|
// performance mode, for a weak GPU) stays on the device it was set on. Kept in browser storage only, a
|
|
// preference used to stay on the one device it was changed on.
|
|
|
|
async function device(browser: Browser, descriptor: (typeof devices)[string]): Promise<Page> {
|
|
const context = await browser.newContext({
|
|
...descriptor,
|
|
baseURL: test.info().project.use.baseURL,
|
|
locale: "en-US",
|
|
storageState: MEMBER_STORAGE,
|
|
});
|
|
return context.newPage();
|
|
}
|
|
|
|
const savedPrefs = (page: Page) =>
|
|
page.waitForResponse(
|
|
(r) => r.url().includes("/me/preferences") && r.request().method() === "PUT",
|
|
);
|
|
|
|
async function pickLayout(page: Page, from: string, to: string): Promise<void> {
|
|
await page.getByRole("button", { name: `Layout: ${from}` }).click();
|
|
await Promise.all([savedPrefs(page), page.getByRole("menuitemradio", { name: to }).click()]);
|
|
await expect(page.getByRole("button", { name: `Layout: ${to}` })).toBeVisible();
|
|
}
|
|
|
|
test("a preference follows the member to another device, a device setting stays", async ({
|
|
browser,
|
|
}) => {
|
|
const desktop = await device(browser, devices["Desktop Chrome"]);
|
|
const ipad = await device(browser, devices["iPad (gen 7) landscape"]);
|
|
try {
|
|
await openApp(desktop);
|
|
await gotoPage(desktop, "channels");
|
|
await pickLayout(desktop, "Table", "Cards");
|
|
await gotoPage(desktop, "settings");
|
|
await desktop.getByRole("switch", { name: "Performance mode" }).click();
|
|
await expect(desktop.locator("html")).toHaveAttribute("data-perf", "1");
|
|
// The Settings prefs save as one payload: the next one (a hints change) would carry performance
|
|
// mode if it went to the account. Waited for, so the iPad below cannot simply load first.
|
|
const hints = desktop.getByRole("switch", { name: "Show hints" });
|
|
const [save] = await Promise.all([savedPrefs(desktop), hints.click()]);
|
|
expect(Object.keys(save.request().postDataJSON() as object)).not.toContain("performanceMode");
|
|
await Promise.all([savedPrefs(desktop), hints.click()]);
|
|
|
|
await openApp(ipad);
|
|
await expect(ipad.locator("html")).not.toHaveAttribute("data-perf", "1");
|
|
await gotoPage(ipad, "channels");
|
|
await expect(ipad.getByRole("button", { name: "Layout: Cards" })).toBeVisible();
|
|
} finally {
|
|
// The member is shared by the suite: put the layout back (performance mode went with the
|
|
// desktop's storage).
|
|
await gotoPage(desktop, "channels");
|
|
await pickLayout(desktop, "Cards", "Table");
|
|
await desktop.context().close();
|
|
await ipad.context().close();
|
|
}
|
|
});
|