From dd419eabfdee6e19dfb0d7699784a3786d6b1762 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 26 Jan 2026 16:56:26 -0800 Subject: [PATCH] Complete emergency access test --- .../emergency-access.play.spec.ts | 81 +++++-------------- .../src/fixtures/auth.fixture.ts | 57 ++++++++----- 2 files changed, 57 insertions(+), 81 deletions(-) diff --git a/apps/web/src/app/auth/emergency-access/emergency-access.play.spec.ts b/apps/web/src/app/auth/emergency-access/emergency-access.play.spec.ts index 50da0d7cf3c..ca5dd90d808 100644 --- a/apps/web/src/app/auth/emergency-access/emergency-access.play.spec.ts +++ b/apps/web/src/app/auth/emergency-access/emergency-access.play.spec.ts @@ -1,67 +1,21 @@ -import { test as base, expect, Page } from "@playwright/test"; +import { expect } from "@playwright/test"; import { + test, EmergencyAccessInviteQuery, Play, - Scene, - SingleUserSceneTemplate, + expectUnlockedAs, } from "@bitwarden/playwright-helpers"; -import { UserId } from "@bitwarden/user-core"; -async function authenticate(page: Page, email: string) { - const scene = await Play.scene(new SingleUserSceneTemplate({ email, premium: true })); +test.describe("Emergency Access", () => { + test("Account takeover", async ({ auth }) => { + const grantee = await auth.authenticate("grantee@bitwarden.com", "asdfasdfasdf", { + premium: false, + }); + const grantor = await auth.authenticate("grantor@bitwarden.com", "asdfasdfasdf", { + premium: true, + }); - await page.goto("/#/login"); - await page.getByRole("textbox", { name: "Email address (required)" }).click(); - await page.getByRole("textbox", { name: "Email address (required)" }).fill(scene.mangle(email)); - await page.getByRole("textbox", { name: "Email address (required)" }).press("Enter"); - await page.getByRole("textbox", { name: "Master password (required)" }).click(); - await page - .getByRole("textbox", { name: "Master password (required)" }) - .fill(scene.mangle("asdfasdfasdf")); - await page.getByRole("button", { name: "Log in with master password" }).click(); - await expect(page.getByRole("button", { name: "Add it later" })).toBeVisible(); - await page.getByRole("button", { name: "Add it later" }).click(); - await expect(page.locator("bit-simple-dialog")).toContainText( - "You can't autofill passwords without the browser extension", - ); - await page.getByRole("link", { name: "Skip to web app" }).click(); - await expect(page.locator("app-vault")).toContainText("There are no items to list. New item"); - - return scene; -} - -type MyFixtures = { - grantee: MyFixture2; - grantor: MyFixture2; -}; - -type MyFixture2 = { - page: Page; - scene: Scene; -}; - -const test = base.extend({ - // Person getting access - grantee: async ({ browser }, use) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const scene = await authenticate(page, "grantee@bitwarden.com"); - await use({ page, scene }); - //await context.close(); - }, - // Person giving access - grantor: async ({ browser }, use) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const scene = await authenticate(page, "grantor@bitwarden.com"); - await use({ page, scene }); - //await context.close(); - }, -}); - -base.describe("Emergency Access", () => { - test.skip("Account takeover", async ({ grantee, grantor }) => { const granteeEmail = grantee.scene.mangle("grantee@bitwarden.com"); // Add a new emergency contact @@ -80,8 +34,8 @@ base.describe("Emergency Access", () => { await grantee.page.goto(`/#${inviteUrl}`); // Confirm the invite - await grantor.page.goto("/#"); await grantor.page.goto("/#/settings/emergency-access"); + await grantor.page.reload(); await expect(await grantor.page.getByRole("cell", { name: granteeEmail })).toBeVisible(); await grantor.page.getByRole("button", { name: "Options" }).click(); await grantor.page.getByRole("menuitem", { name: "Confirm" }).click(); @@ -89,20 +43,21 @@ base.describe("Emergency Access", () => { // Request access await grantee.page.goto("/#/settings/emergency-access"); + await grantee.page.reload(); await grantee.page.getByRole("button", { name: "Options" }).click(); await grantee.page.getByRole("menuitem", { name: "Request Access" }).click(); await grantee.page.getByRole("button", { name: "Request Access" }).click(); // Approve access - await grantor.page.goto("/#"); await grantor.page.goto("/#/settings/emergency-access"); + await grantor.page.reload(); await grantor.page.getByRole("button", { name: "Options" }).click(); await grantor.page.getByRole("menuitem", { name: "Approve" }).click(); await grantor.page.getByRole("button", { name: "Approve" }).click(); // Initiate takeover - await grantee.page.goto("/#"); await grantee.page.goto("/#/settings/emergency-access"); + await grantee.page.reload(); await grantee.page.getByRole("button", { name: "Options" }).click(); await grantee.page.getByRole("menuitem", { name: "Takeover" }).click(); @@ -115,8 +70,8 @@ base.describe("Emergency Access", () => { await grantee.page.getByRole("button", { name: "Save" }).click(); await grantee.page.getByRole("button", { name: "Yes" }).click(); - // TODO: Confirm the new password works by logging out and back in - - // await new Promise(() => {}); + // Confirm with + const { page: newGranteePage } = await auth.authenticateForScene(grantee.scene, "qwertyqwerty"); + await expectUnlockedAs(granteeEmail, newGranteePage); }); }); diff --git a/libs/playwright-helpers/src/fixtures/auth.fixture.ts b/libs/playwright-helpers/src/fixtures/auth.fixture.ts index 9831cf2afc5..c3e22b3a3ec 100644 --- a/libs/playwright-helpers/src/fixtures/auth.fixture.ts +++ b/libs/playwright-helpers/src/fixtures/auth.fixture.ts @@ -4,11 +4,14 @@ import * as path from "path"; import { Browser, Page, test, TestFixture } from "@playwright/test"; import { webServerBaseUrl } from "@playwright-config"; import * as playwright from "playwright"; +import { Except, Simplify } from "type-fest"; // Playwright doesn't expose this type, so we duplicate it here type BrowserName = "chromium" | "firefox" | "webkit"; import { Play, SingleUserScene, SingleUserSceneTemplate } from "@bitwarden/playwright-helpers"; +import { extractTUpType } from "../scene-templates/scene-template"; + import { addInitScriptForPlayId } from "./page-extension"; const hostname = new URL(webServerBaseUrl).hostname; @@ -41,6 +44,8 @@ type AuthenticatedContext = { scene: SingleUserScene; }; +type SessionOptions = Simplify, "email">>; + /** * A map of already authenticated emails to their scenes. */ @@ -74,16 +79,14 @@ export class AuthFixture { } } - async page(): Promise { - if (!this._page) { - if (!this._browser) { - await this.init(); - } - const context = await this._browser.newContext(); - this._page = await context.newPage(); - await addInitScriptForPlayId(this._page, process.env.PLAY_ID!); + async newPage(): Promise { + if (!this._browser) { + await this.init(); } - return this._page; + const context = await this._browser.newContext(); + const page = await context.newPage(); + await addInitScriptForPlayId(page, process.env.PLAY_ID!); + return page; } /** @@ -95,17 +98,21 @@ export class AuthFixture { * @param password password of the user * @returns The authenticated page and scene used to scaffold the user */ - async authenticate(email: string, password: string): Promise { + async authenticate( + email: string, + password: string, + options: SessionOptions = {}, + ): Promise { if (AuthenticatedEmails.has(email)) { return await this.resumeSession(email, password); } // start a new session - return await this.newSession(email, password); + return await this.newSession(email, password, options); } async resumeSession(email: string, password: string): Promise { - const page = await this.page(); + const page = await this.newPage(); if (AuthenticatedEmails.get(email)!.password !== password) { throw new Error( `Email ${email} is already authenticated with a different password (${ @@ -133,15 +140,26 @@ export class AuthFixture { }; } - async newSession(email: string, password: string): Promise { - const page = await this.page(); - const scene = await Play.scene(new SingleUserSceneTemplate({ email })); + async newSession( + email: string, + password: string, + options: SessionOptions = {}, + ): Promise { + const scene = await Play.scene(new SingleUserSceneTemplate({ ...options, email })); + + return await this.authenticateForScene(scene, password); + } + + async authenticateForScene( + scene: SingleUserScene, + password: string, + ): Promise { + const page = await this.newPage(); + const email = scene.upArgs.email; const mangledEmail = scene.mangle(email); await page.goto("/#/login"); - await page - .getByRole("textbox", { name: "Email address (required)" }) - .fill(scene.mangle("test@example.com")); + await page.getByRole("textbox", { name: "Email address (required)" }).fill(scene.mangle(email)); await page.getByRole("textbox", { name: "Email address (required)" }).press("Enter"); await page .getByRole("textbox", { name: "Master password (required)" }) @@ -149,6 +167,9 @@ export class AuthFixture { await page.getByRole("button", { name: "Log in with master password" }).click(); await page.getByRole("button", { name: "Add it later" }).click(); await page.getByRole("link", { name: "Skip to web app" }).click(); + if (!scene.upArgs.premium) { + await page.getByRole("button", { name: "Continue without upgrading" }).click(); + } // Store the scene for future use AuthenticatedEmails.set(email, { email, password, scene });