import { Locator, Page, expect } from '@playwright/test'; /** * 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. * * Fields are scoped to the dialog. Several of them ("Password", "First name") * share a label with the auth modal, and the two can both be in the DOM while * one is closing. */ export class AccountModal { readonly dialog: Locator; readonly logOutButton: Locator; readonly orderHistoryButton: Locator; readonly closeButton: Locator; readonly resendVerificationButton: Locator; readonly firstName: Locator; readonly lastName: Locator; readonly saveNameButton: Locator; readonly currentPassword: Locator; readonly newPassword: Locator; readonly confirmNewPassword: Locator; readonly newEmail: Locator; readonly passwordForEmailChange: Locator; constructor(private readonly page: Page) { this.dialog = page.getByRole('dialog', { name: 'My Account' }); this.logOutButton = this.dialog.getByRole('button', { name: 'Log out' }); this.orderHistoryButton = this.dialog.getByRole('button', { name: 'View order history' }); this.closeButton = this.dialog.getByRole('button', { name: 'Close' }); this.resendVerificationButton = this.dialog.getByRole('button', { name: 'Send it again' }); this.firstName = this.dialog.getByLabel('First name', { exact: true }); this.lastName = this.dialog.getByLabel('Last name', { exact: true }); this.saveNameButton = this.dialog.getByRole('button', { name: 'Save name' }); this.currentPassword = this.dialog.getByLabel('Current password', { exact: true }); this.newPassword = this.dialog.getByLabel('New password', { exact: true }); this.confirmNewPassword = this.dialog.getByLabel('Confirm new password', { exact: true }); this.newEmail = this.dialog.getByLabel('New email address', { exact: true }); this.passwordForEmailChange = this.dialog.getByLabel('Your password', { exact: true }); } /** * Opens the account view and waits for it. * * 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 * exists finds nothing. */ async open(): Promise { await this.page.goto('/account'); await expect(this.dialog).toBeVisible(); } async logOut(): Promise { await this.logOutButton.click(); } /** * Opens the account view and logs out, which is the only route to signing out * — there is no header control for it. * * Does not wait for the result. Several tests assert different things about * what logging out does: the URL it lands on, the header it leaves behind, a * failure it reports. Waiting here would make one of those the action's * contract and quietly weaken the others. */ async openAndLogOut(): Promise { await this.open(); await this.logOut(); } /** The address the account view shows, which is how a test knows whose it is. */ emailText(email: string): Locator { return this.dialog.getByText(email); } }