test(e2e): convert the account specs onto page objects (#137)

account-modal and account-details, taking two more copies of "register a customer" and two more of the `accountModal(page)` helper that every account-touching spec had rewritten.

Both files had grown their own vocabulary for the same dialog. account-details reached for `modal.getByLabel('Current password', { exact: true })` and its five siblings inline in each test, so a change to the account form meant editing six places in one file and more in the next. Those are named locators now, and the three multi-step operations — saving a name, changing a password, changing an email — are actions, because each is a disclosure to open and three fields to fill before the button does anything.

AccountModal.open() gains the 20s timeout the header already had, and for the same reason. Arriving at /account means booting the app and resolving the session against the server. The old specs never noticed because they registered through the form first, which loaded the app and confirmed the session before navigating; taking the API-registered `customer` fixture arrives cold, and the 5s default is comfortably beaten on an idle machine and missed on a loaded one.

Verified: 18/18 across three serial repeats, and 12/13 in parallel. The one failure is `changes the email address and marks it unverified again`, which fails identically on the unconverted file and passes whenever the suite is not saturated — the same load-related flakiness as the rest of the family, not something this commit introduced.

Refs #137
This commit is contained in:
2026-08-23 17:46:45 -05:00
parent ed679986de
commit 4b0c01068f
3 changed files with 179 additions and 183 deletions
+49 -96
View File
@@ -1,77 +1,40 @@
import { test, expect, Page } from './fixtures'; import { test, expect, uniqueEmail } from './fixtures';
const PASSWORD = 'supersecret123'; const NEW_PASSWORD = 'a-brand-new-password';
const uniqueEmail = () => `details-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}@example.com`;
// Same generous wait as the other account specs: registration is a bcrypt
// round-trip, not a render, and runs past Playwright's 5s default when the
// suite's workers all register at once.
async function registerCustomer(page: Page): Promise<string> {
const email = uniqueEmail();
await page.goto('/register');
await page.getByRole('textbox', { name: 'Email' }).fill(email);
await page.getByRole('textbox', { name: 'First name' }).fill('Test');
await page.getByRole('textbox', { name: 'Last name' }).fill('Customer');
await page.getByLabel('Password').fill(PASSWORD);
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible({ timeout: 20000 });
return email;
}
const accountModal = (page: Page) => page.getByRole('dialog', { name: 'My Account' });
async function signIn(page: Page, email: string, password: string) {
await page.goto('/login');
await page.getByRole('textbox', { name: 'Email' }).fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('dialog', { name: 'Log in' }).getByRole('button', { name: 'Log in' }).click();
}
test.describe('Managing your own details', () => { test.describe('Managing your own details', () => {
test('saves a new name, and it survives a reload', async ({ page }) => { test('saves a new name, and it survives a reload', async ({ page, customer, accountModal }) => {
await registerCustomer(page); await accountModal.open();
await page.goto('/account'); await accountModal.saveName('Ada', 'Lovelace');
const modal = accountModal(page);
await modal.getByLabel('First name', { exact: true }).fill('Ada');
await modal.getByLabel('Last name', { exact: true }).fill('Lovelace');
await modal.getByRole('button', { name: 'Save name' }).click();
await expect(page.getByText('Name updated')).toBeVisible(); await expect(page.getByText('Name updated')).toBeVisible();
// Reloaded rather than re-read from component state, which would pass even // Reloaded rather than re-read from component state, which would pass even
// if nothing had been persisted. // if nothing had been persisted.
await page.reload(); await page.reload();
await expect(accountModal(page).getByLabel('First name', { exact: true })).toHaveValue('Ada'); await expect(accountModal.firstName).toHaveValue('Ada');
await expect(accountModal(page).getByLabel('Last name', { exact: true })).toHaveValue('Lovelace'); await expect(accountModal.lastName).toHaveValue('Lovelace');
}); });
test('refuses to save a blank name', async ({ page }) => { test('refuses to save a blank name', async ({ customer, accountModal }) => {
await registerCustomer(page); await accountModal.open();
await page.goto('/account'); await accountModal.saveName('');
const modal = accountModal(page); await expect(accountModal.dialog.getByText('First name is required')).toBeVisible();
await modal.getByLabel('First name', { exact: true }).fill('');
await modal.getByRole('button', { name: 'Save name' }).click();
await expect(modal.getByText('First name is required')).toBeVisible();
}); });
// The assertion that matters for a password change: not that the form said // The assertion that matters for a password change: not that the form said
// something reassuring, but that the old credential has actually stopped // something reassuring, but that the old credential has actually stopped
// opening the account. // opening the account.
test('changes the password, leaving the old one dead and the new one working', async ({ page }) => { test('changes the password, leaving the old one dead and the new one working', async ({
const email = await registerCustomer(page); page,
const newPassword = 'a-brand-new-password'; customer,
await page.goto('/account'); accountModal,
authModal,
const modal = accountModal(page); header
await modal.getByRole('button', { name: 'Change your password' }).click(); }) => {
await modal.getByLabel('Current password', { exact: true }).fill(PASSWORD); await accountModal.open();
await modal.getByLabel('New password', { exact: true }).fill(newPassword); await accountModal.changePassword(customer.password, NEW_PASSWORD);
await modal.getByLabel('Confirm new password', { exact: true }).fill(newPassword);
await modal.getByRole('button', { name: 'Change password' }).click();
await expect(page.getByText('Password changed. Other devices have been signed out.')).toBeVisible(); await expect(page.getByText('Password changed. Other devices have been signed out.')).toBeVisible();
@@ -80,63 +43,53 @@ test.describe('Managing your own details', () => {
// same modal is otherwise also a match. // same modal is otherwise also a match.
await expect(page.getByRole('button', { name: 'My Account', exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: 'My Account', exact: true })).toBeVisible();
// Logging out from inside the open modal, which is where the control lives. await accountModal.logOut();
await page.getByRole('button', { name: 'Log out' }).click();
await expect(page).toHaveURL(/\/$/, { timeout: 20000 }); await expect(page).toHaveURL(/\/$/, { timeout: 20000 });
await signIn(page, email, PASSWORD); await authModal.gotoLogIn();
await authModal.logIn(customer.email, customer.password);
await expect(page.getByText('invalid email or password')).toBeVisible(); await expect(page.getByText('invalid email or password')).toBeVisible();
await signIn(page, email, newPassword); await authModal.logIn(customer.email, NEW_PASSWORD);
await expect(page.getByRole('button', { name: 'My Account', exact: true })) await header.waitForSignedIn();
.toBeVisible({ timeout: 20000 });
}); });
test('refuses a password change when the current password is wrong', async ({ page }) => { test('refuses a password change when the current password is wrong', async ({
await registerCustomer(page); customer,
await page.goto('/account'); accountModal
}) => {
await accountModal.open();
await accountModal.changePassword('not-the-password', 'another-password');
const modal = accountModal(page); await expect(accountModal.dialog.getByText('current password is incorrect')).toBeVisible();
await modal.getByRole('button', { name: 'Change your password' }).click();
await modal.getByLabel('Current password', { exact: true }).fill('not-the-password');
await modal.getByLabel('New password', { exact: true }).fill('another-password');
await modal.getByLabel('Confirm new password', { exact: true }).fill('another-password');
await modal.getByRole('button', { name: 'Change password' }).click();
await expect(modal.getByText('current password is incorrect')).toBeVisible();
}); });
test('changes the email address and marks it unverified again', async ({ page }) => { test('changes the email address and marks it unverified again', async ({
await registerCustomer(page); page,
customer,
accountModal
}) => {
const nextEmail = uniqueEmail(); const nextEmail = uniqueEmail();
await page.goto('/account'); await accountModal.open();
await accountModal.changeEmail(nextEmail, customer.password);
const modal = accountModal(page);
await modal.getByRole('button', { name: 'Change your email address' }).click();
await modal.getByLabel('New email address', { exact: true }).fill(nextEmail);
await modal.getByLabel('Your password', { exact: true }).fill(PASSWORD);
await modal.getByRole('button', { name: 'Change email' }).click();
await expect(page.getByText('Check the new address for a verification link.')).toBeVisible(); await expect(page.getByText('Check the new address for a verification link.')).toBeVisible();
await expect(modal).toContainText(nextEmail); await expect(accountModal.dialog).toContainText(nextEmail);
// A changed address is unverified by definition, and the account view has // A changed address is unverified by definition, and the account view has
// to say so or the customer has no way to know a link is waiting. // to say so or the customer has no way to know a link is waiting.
await expect(modal.getByText('Email not verified')).toBeVisible(); await expect(accountModal.notVerifiedNotice).toBeVisible();
}); });
// A live session is not enough to move the address a password reset goes to, // A live session is not enough to move the address a password reset goes to,
// which is the whole reason the field is there. // which is the whole reason the field is there.
test('refuses an email change when the password is wrong, and keeps the old address', async ({ page }) => { test('refuses an email change when the password is wrong, and keeps the old address', async ({
const email = await registerCustomer(page); customer,
await page.goto('/account'); accountModal
}) => {
await accountModal.open();
await accountModal.changeEmail(uniqueEmail(), 'not-the-password');
const modal = accountModal(page); await expect(accountModal.dialog.getByText('current password is incorrect')).toBeVisible();
await modal.getByRole('button', { name: 'Change your email address' }).click(); await expect(accountModal.dialog).toContainText(customer.email);
await modal.getByLabel('New email address', { exact: true }).fill(uniqueEmail());
await modal.getByLabel('Your password', { exact: true }).fill('not-the-password');
await modal.getByRole('button', { name: 'Change email' }).click();
await expect(modal.getByText('current password is incorrect')).toBeVisible();
await expect(modal).toContainText(email);
}); });
}); });
+63 -83
View File
@@ -1,136 +1,116 @@
import { test, expect, Page } from './fixtures'; import { test, expect } from './fixtures';
const PASSWORD = 'supersecret123';
const uniqueEmail = () => `account-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}@example.com`;
// Registering closes the auth modal and returns to the storefront, signed in.
// Returns the address so a test can assert the right account is shown.
//
// The wait is generous because registration is a bcrypt round-trip rather than
// a render: about half a second unloaded, and past Playwright's 5s default when
// the suite's workers all register at once.
async function registerCustomer(page: Page): Promise<string> {
const email = uniqueEmail();
await page.goto('/register');
await page.getByRole('textbox', { name: 'Email' }).fill(email);
await page.getByRole('textbox', { name: 'First name' }).fill('Test');
await page.getByRole('textbox', { name: 'Last name' }).fill('Customer');
await page.getByLabel('Password').fill(PASSWORD);
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible({ timeout: 20000 });
return email;
}
const accountModal = (page: Page) => page.getByRole('dialog', { name: 'My Account' });
async function closeAccount(page: Page) {
await accountModal(page).getByRole('button', { name: 'Close' }).click();
await expect(accountModal(page)).toBeHidden();
}
test.describe('My Account opens as a modal', () => { test.describe('My Account opens as a modal', () => {
test('opens over the storefront and closes back to it, filters and all', async ({ page }) => { test('opens over the storefront and closes back to it, filters and all', async ({
await registerCustomer(page); page,
customer,
accountModal,
header
}) => {
// A filtered view, to prove closing restores where the customer actually // A filtered view, to prove closing restores where the customer actually
// was rather than a bare storefront. // was rather than a bare storefront.
await page.goto('/?max_price=50000'); await page.goto('/?max_price=50000');
await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible(); await header.waitForSignedIn();
await page.getByRole('button', { name: 'My Account' }).click(); await accountModal.openFromHeader();
await expect(accountModal(page)).toBeVisible();
// Still a real, linkable URL rather than hidden view state. // Still a real, linkable URL rather than hidden view state.
await expect(page).toHaveURL(/\/account/); await expect(page).toHaveURL(/\/account/);
await closeAccount(page); await accountModal.close();
await expect(page).toHaveURL(/max_price=50000/); await expect(page).toHaveURL(/max_price=50000/);
}); });
test('the browser back button closes it, the same as the close control', async ({ page }) => { test('the browser back button closes it, the same as the close control', async ({
await registerCustomer(page); page,
customer,
accountModal,
header
}) => {
await page.goto('/?max_price=50000'); await page.goto('/?max_price=50000');
await page.getByRole('button', { name: 'My Account' }).click(); await header.waitForSignedIn();
await expect(accountModal(page)).toBeVisible(); await accountModal.openFromHeader();
await page.goBack(); await page.goBack();
await expect(accountModal(page)).toBeHidden(); await expect(accountModal.dialog).toBeHidden();
await expect(page).toHaveURL(/max_price=50000/); await expect(page).toHaveURL(/max_price=50000/);
}); });
test('a direct visit renders the storefront behind it, so closing lands somewhere real', async ({ page }) => { test('a direct visit renders the storefront behind it, so closing lands somewhere real', async ({
await registerCustomer(page); page,
customer,
accountModal,
header
}) => {
// A bookmark, or the link in a verification email. There is no page behind // A bookmark, or the link in a verification email. There is no page behind
// in this case, which is what used to make /account a dead end. // in this case, which is what used to make /account a dead end.
await page.goto('/account'); await accountModal.open();
await expect(accountModal(page)).toBeVisible(); await expect(header.siteTitle).toBeVisible();
await expect(page.getByRole('heading', { name: 'Redefined Designs' })).toBeVisible();
await closeAccount(page); await accountModal.close();
await expect(page).toHaveURL(/\/$/); await expect(page).toHaveURL(/\/$/);
await expect(page.getByRole('button', { name: 'My Account' })).toBeVisible(); await expect(header.myAccountButton).toBeVisible();
}); });
test('survives a reload, since it is a route rather than view state', async ({ page }) => { test('survives a reload, since it is a route rather than view state', async ({
const email = await registerCustomer(page); page,
customer,
accountModal,
header
}) => {
await page.goto('/?max_price=50000'); await page.goto('/?max_price=50000');
await page.getByRole('button', { name: 'My Account' }).click(); await header.waitForSignedIn();
await expect(accountModal(page)).toBeVisible(); await accountModal.openFromHeader();
await page.reload(); await page.reload();
await expect(accountModal(page)).toBeVisible(); await expect(accountModal.dialog).toBeVisible();
await expect(accountModal(page)).toContainText(email); await expect(accountModal.dialog).toContainText(customer.email);
}); });
test('shows the signed-in account and its settings', async ({ page }) => { test('shows the signed-in account and its settings', async ({ customer, accountModal }) => {
const email = await registerCustomer(page); await accountModal.open();
await page.goto('/account'); await expect(accountModal.dialog).toContainText(customer.email);
const modal = accountModal(page);
await expect(modal).toContainText(email);
// The orders table lives at /orders now. What the account view still owes // The orders table lives at /orders now. What the account view still owes
// the customer is a way to reach it. // the customer is a way to reach it.
await expect(modal.getByRole('button', { name: 'View order history' })).toBeVisible(); await expect(accountModal.orderHistoryButton).toBeVisible();
// Scoped to the modal: the storefront behind it has a theme switch of its // Scoped to the modal: the storefront behind it has a theme switch of its
// own, so an unscoped switch locator would be ambiguous. // own, so an unscoped switch locator would be ambiguous.
await expect(modal.getByRole('switch')).toHaveCount(2); await expect(accountModal.themeSwitches).toHaveCount(2);
}); });
test('deleting the account does not leave the page behind it looking signed in', async ({ page }) => { test('deleting the account does not leave the page behind it looking signed in', async ({
await registerCustomer(page); page,
await page.goto('/account'); customer,
accountModal,
await accountModal(page).getByRole('button', { name: 'Delete my account' }).click(); header
await page.getByRole('dialog', { name: 'Delete your account?' }) }) => {
.getByRole('button', { name: 'Delete my account' }).click(); await accountModal.open();
await accountModal.deleteAccount();
// The storefront is rendered behind the modal, so a session left in place // The storefront is rendered behind the modal, so a session left in place
// would visibly go on offering My Account for an account that is gone. // would visibly go on offering My Account for an account that is gone.
await expect(page.getByRole('button', { name: 'Sign up' })).toBeVisible(); await expect(header.signUpButton).toBeVisible();
await expect(page.getByRole('button', { name: 'My Account' })).toBeHidden(); await expect(header.myAccountButton).toBeHidden();
await expect(page).toHaveURL(/\/$/); await expect(page).toHaveURL(/\/$/);
}); });
test('stays usable on a phone, with the close control in reach', async ({ page }) => { test('stays usable on a phone, with the close control in reach', async ({
await registerCustomer(page); page,
customer,
accountModal
}) => {
await page.setViewportSize({ width: 390, height: 664 }); await page.setViewportSize({ width: 390, height: 664 });
await page.goto('/account'); await accountModal.open();
const modal = accountModal(page);
await expect(modal).toBeVisible();
// The view is taller than the viewport, so the body scrolls rather than // The view is taller than the viewport, so the body scrolls rather than
// pushing the title and close control off-screen. // pushing the title and close control off-screen.
await expect(modal.getByRole('button', { name: 'Close' })).toBeInViewport(); await expect(accountModal.closeButton).toBeInViewport();
await expect(modal.getByRole('button', { name: 'View order history' })).toBeVisible(); await expect(accountModal.orderHistoryButton).toBeVisible();
await closeAccount(page); await accountModal.close();
await expect(page).toHaveURL(/\/$/); await expect(page).toHaveURL(/\/$/);
}); });
}); });
+67 -4
View File
@@ -4,9 +4,11 @@ import { Locator, Page, expect } from '@playwright/test';
* The customer's own account view, which is a modal over the storefront rather * The customer's own account view, which is a modal over the storefront rather
* than a page of its own — /account renders the storefront with this on top. * than a page of its own — /account renders the storefront with this on top.
* *
* Fields are scoped to the dialog. Several of them ("Password", "First name") * Everything is scoped to the dialog. Several controls have a twin on the page
* share a label with the auth modal, and the two can both be in the DOM while * behind it: the storefront has its own theme switch, and "Password" and "First
* one is closing. * name" share a label with the auth modal, which can be in the DOM while one of
* the two is closing. An unscoped locator matches both and fails on strict mode,
* which is a confusing way to learn that a modal is a modal.
*/ */
export class AccountModal { export class AccountModal {
readonly dialog: Locator; readonly dialog: Locator;
@@ -14,17 +16,26 @@ export class AccountModal {
readonly orderHistoryButton: Locator; readonly orderHistoryButton: Locator;
readonly closeButton: Locator; readonly closeButton: Locator;
readonly resendVerificationButton: Locator; readonly resendVerificationButton: Locator;
readonly themeSwitches: Locator;
readonly notVerifiedNotice: Locator;
readonly firstName: Locator; readonly firstName: Locator;
readonly lastName: Locator; readonly lastName: Locator;
readonly saveNameButton: Locator; readonly saveNameButton: Locator;
readonly changePasswordDisclosure: Locator;
readonly currentPassword: Locator; readonly currentPassword: Locator;
readonly newPassword: Locator; readonly newPassword: Locator;
readonly confirmNewPassword: Locator; readonly confirmNewPassword: Locator;
readonly submitPasswordChangeButton: Locator;
readonly changeEmailDisclosure: Locator;
readonly newEmail: Locator; readonly newEmail: Locator;
readonly passwordForEmailChange: Locator; readonly passwordForEmailChange: Locator;
readonly submitEmailChangeButton: Locator;
readonly deleteAccountButton: Locator;
readonly confirmDeleteDialog: Locator;
constructor(private readonly page: Page) { constructor(private readonly page: Page) {
this.dialog = page.getByRole('dialog', { name: 'My Account' }); this.dialog = page.getByRole('dialog', { name: 'My Account' });
@@ -32,17 +43,26 @@ export class AccountModal {
this.orderHistoryButton = this.dialog.getByRole('button', { name: 'View order history' }); this.orderHistoryButton = this.dialog.getByRole('button', { name: 'View order history' });
this.closeButton = this.dialog.getByRole('button', { name: 'Close' }); this.closeButton = this.dialog.getByRole('button', { name: 'Close' });
this.resendVerificationButton = this.dialog.getByRole('button', { name: 'Send it again' }); this.resendVerificationButton = this.dialog.getByRole('button', { name: 'Send it again' });
this.themeSwitches = this.dialog.getByRole('switch');
this.notVerifiedNotice = this.dialog.getByText('Email not verified');
this.firstName = this.dialog.getByLabel('First name', { exact: true }); this.firstName = this.dialog.getByLabel('First name', { exact: true });
this.lastName = this.dialog.getByLabel('Last name', { exact: true }); this.lastName = this.dialog.getByLabel('Last name', { exact: true });
this.saveNameButton = this.dialog.getByRole('button', { name: 'Save name' }); this.saveNameButton = this.dialog.getByRole('button', { name: 'Save name' });
this.changePasswordDisclosure = this.dialog.getByRole('button', { name: 'Change your password' });
this.currentPassword = this.dialog.getByLabel('Current password', { exact: true }); this.currentPassword = this.dialog.getByLabel('Current password', { exact: true });
this.newPassword = this.dialog.getByLabel('New password', { exact: true }); this.newPassword = this.dialog.getByLabel('New password', { exact: true });
this.confirmNewPassword = this.dialog.getByLabel('Confirm new password', { exact: true }); this.confirmNewPassword = this.dialog.getByLabel('Confirm new password', { exact: true });
this.submitPasswordChangeButton = this.dialog.getByRole('button', { name: 'Change password' });
this.changeEmailDisclosure = this.dialog.getByRole('button', { name: 'Change your email address' });
this.newEmail = this.dialog.getByLabel('New email address', { exact: true }); this.newEmail = this.dialog.getByLabel('New email address', { exact: true });
this.passwordForEmailChange = this.dialog.getByLabel('Your password', { exact: true }); this.passwordForEmailChange = this.dialog.getByLabel('Your password', { exact: true });
this.submitEmailChangeButton = this.dialog.getByRole('button', { name: 'Change email' });
this.deleteAccountButton = this.dialog.getByRole('button', { name: 'Delete my account' });
this.confirmDeleteDialog = page.getByRole('dialog', { name: 'Delete your account?' });
} }
/** /**
@@ -51,10 +71,26 @@ export class AccountModal {
* The wait is the action's contract rather than an assertion: everything a * The wait is the action's contract rather than an assertion: everything a
* caller does next is scoped to this dialog, and a locator resolved before it * caller does next is scoped to this dialog, and a locator resolved before it
* exists finds nothing. * exists finds nothing.
*
* The timeout is generous for the same reason the header's is. Arriving here
* means booting the app and resolving the session against the server, and the
* 5s default is comfortably beaten on an idle machine and missed on a loaded
* one — the recipe for a test that fails only when the suite is busy.
*/ */
async open(): Promise<void> { async open(): Promise<void> {
await this.page.goto('/account'); await this.page.goto('/account');
await expect(this.dialog).toBeVisible(); await expect(this.dialog).toBeVisible({ timeout: 20000 });
}
/** Opens it from the header, from wherever the customer was browsing. */
async openFromHeader(): Promise<void> {
await this.page.getByRole('button', { name: 'My Account' }).click();
await expect(this.dialog).toBeVisible({ timeout: 20000 });
}
async close(): Promise<void> {
await this.closeButton.click();
await expect(this.dialog).toBeHidden();
} }
async logOut(): Promise<void> { async logOut(): Promise<void> {
@@ -75,6 +111,33 @@ export class AccountModal {
await this.logOut(); await this.logOut();
} }
async saveName(firstName: string, lastName?: string): Promise<void> {
await this.firstName.fill(firstName);
if (lastName !== undefined) await this.lastName.fill(lastName);
await this.saveNameButton.click();
}
/** Both password fields sit behind a disclosure, so it has to be opened first. */
async changePassword(current: string, next: string): Promise<void> {
await this.changePasswordDisclosure.click();
await this.currentPassword.fill(current);
await this.newPassword.fill(next);
await this.confirmNewPassword.fill(next);
await this.submitPasswordChangeButton.click();
}
async changeEmail(newEmail: string, password: string): Promise<void> {
await this.changeEmailDisclosure.click();
await this.newEmail.fill(newEmail);
await this.passwordForEmailChange.fill(password);
await this.submitEmailChangeButton.click();
}
async deleteAccount(): Promise<void> {
await this.deleteAccountButton.click();
await this.confirmDeleteDialog.getByRole('button', { name: 'Delete my account' }).click();
}
/** The address the account view shows, which is how a test knows whose it is. */ /** The address the account view shows, which is how a test knows whose it is. */
emailText(email: string): Locator { emailText(email: string): Locator {
return this.dialog.getByText(email); return this.dialog.getByText(email);