diff --git a/packages/desktop-client/e2e/budget.mobile.test.ts b/packages/desktop-client/e2e/budget.mobile.test.ts index 9b151d24bba..51b30fd8ef9 100644 --- a/packages/desktop-client/e2e/budget.mobile.test.ts +++ b/packages/desktop-client/e2e/budget.mobile.test.ts @@ -60,6 +60,9 @@ async function setBudgetAverage( await budgetPage.goToPreviousMonth(); const spentButton = await budgetPage.getButtonForSpent(categoryName); const spent = await spentButton.textContent(); + if (!spent) { + throw new Error('Failed to get spent amount'); + } totalSpent += currencyToAmount(spent) ?? 0; } @@ -76,7 +79,7 @@ async function setBudgetAverage( return averageSpent; } -const budgetTypes = ['Envelope', 'Tracking']; +const budgetTypes = ['Envelope', 'Tracking'] as const; budgetTypes.forEach(budgetType => { test.describe(`Mobile Budget [${budgetType}]`, () => { @@ -280,6 +283,10 @@ budgetTypes.forEach(budgetType => { const lastMonthBudget = await budgetedButton.textContent(); + if (!lastMonthBudget) { + throw new Error('Failed to get last month budget'); + } + await budgetPage.goToNextMonth(); await copyLastMonthBudget(budgetPage, categoryName); diff --git a/packages/desktop-client/e2e/page-models/account-page.ts b/packages/desktop-client/e2e/page-models/account-page.ts index 1644799f29e..ebbee3f0068 100644 --- a/packages/desktop-client/e2e/page-models/account-page.ts +++ b/packages/desktop-client/e2e/page-models/account-page.ts @@ -56,8 +56,8 @@ export class AccountPage { this.selectTooltip = this.page.getByTestId('transactions-select-tooltip'); } - async waitFor() { - await this.transactionTable.waitFor(); + async waitFor(...options: Parameters) { + await this.transactionTable.waitFor(...options); } /** diff --git a/packages/desktop-client/e2e/page-models/mobile-account-page.ts b/packages/desktop-client/e2e/page-models/mobile-account-page.ts index 91378373635..4c7e312e776 100644 --- a/packages/desktop-client/e2e/page-models/mobile-account-page.ts +++ b/packages/desktop-client/e2e/page-models/mobile-account-page.ts @@ -26,8 +26,8 @@ export class MobileAccountPage { }); } - async waitFor() { - await this.transactionList.waitFor(); + async waitFor(...options: Parameters) { + await this.transactionList.waitFor(...options); } /** diff --git a/packages/desktop-client/e2e/page-models/mobile-accounts-page.ts b/packages/desktop-client/e2e/page-models/mobile-accounts-page.ts index 6df5f6875bb..571244535fc 100644 --- a/packages/desktop-client/e2e/page-models/mobile-accounts-page.ts +++ b/packages/desktop-client/e2e/page-models/mobile-accounts-page.ts @@ -14,14 +14,14 @@ export class MobileAccountsPage { this.accountListItems = this.accountList.getByTestId('account-list-item'); } - async waitFor() { - await this.accountList.waitFor(); + async waitFor(...options: Parameters) { + await this.accountList.waitFor(...options); } /** * Get the name and balance of the nth account */ - async getNthAccount(idx) { + async getNthAccount(idx: number) { const accountRow = this.accountListItems.nth(idx); return { @@ -33,7 +33,7 @@ export class MobileAccountsPage { /** * Click on the n-th account to open it up */ - async openNthAccount(idx) { + async openNthAccount(idx: number) { await this.accountListItems.nth(idx).click(); return new MobileAccountPage(this.page); diff --git a/packages/desktop-client/e2e/page-models/mobile-budget-menu-modal.ts b/packages/desktop-client/e2e/page-models/mobile-budget-menu-modal.ts index 46a9ebf6f9c..5f39f117213 100644 --- a/packages/desktop-client/e2e/page-models/mobile-budget-menu-modal.ts +++ b/packages/desktop-client/e2e/page-models/mobile-budget-menu-modal.ts @@ -38,8 +38,8 @@ export class BudgetMenuModal { await this.heading.getByRole('button', { name: 'Close' }).click(); } - async setBudgetAmount(newAmount) { - await this.budgetAmountInput.fill(String(newAmount)); + async setBudgetAmount(newAmount: string) { + await this.budgetAmountInput.fill(newAmount); await this.budgetAmountInput.blur(); await this.close(); } diff --git a/packages/desktop-client/e2e/page-models/mobile-budget-page.ts b/packages/desktop-client/e2e/page-models/mobile-budget-page.ts index 49549d930e1..daa7804b27d 100644 --- a/packages/desktop-client/e2e/page-models/mobile-budget-page.ts +++ b/packages/desktop-client/e2e/page-models/mobile-budget-page.ts @@ -102,8 +102,8 @@ export class MobileBudgetPage { : 'Tracking'; } - async waitFor(options) { - await this.budgetTable.waitFor(options); + async waitFor(...options: Parameters) { + await this.budgetTable.waitFor(...options); } async toggleVisibleColumns({ @@ -319,6 +319,9 @@ export class MobileBudgetPage { async openEnvelopeBudgetSummary() { const budgetSummaryButton = await this.#getButtonForEnvelopeBudgetSummary(); + if (!budgetSummaryButton) { + throw new Error('Envelope budget summary button not found.'); + } await budgetSummaryButton.click(); return new EnvelopeBudgetSummaryModal(this.page.getByRole('dialog')); @@ -350,6 +353,9 @@ export class MobileBudgetPage { async openTrackingBudgetSummary() { const budgetSummaryButton = await this.#getButtonForTrackingBudgetSummary(); + if (!budgetSummaryButton) { + throw new Error('Tracking budget summary button not found.'); + } await budgetSummaryButton.click(); return new TrackingBudgetSummaryModal(this.page.getByRole('dialog')); diff --git a/packages/desktop-client/e2e/page-models/mobile-edit-notes-modal.ts b/packages/desktop-client/e2e/page-models/mobile-edit-notes-modal.ts index e81c9da37b5..845c8dbfc3f 100644 --- a/packages/desktop-client/e2e/page-models/mobile-edit-notes-modal.ts +++ b/packages/desktop-client/e2e/page-models/mobile-edit-notes-modal.ts @@ -20,7 +20,7 @@ export class EditNotesModal { await this.heading.getByRole('button', { name: 'Close' }).click(); } - async updateNotes(notes) { + async updateNotes(notes: string) { await this.textArea.fill(notes); await this.saveNotesButton.click(); } diff --git a/packages/desktop-client/e2e/page-models/mobile-navigation.ts b/packages/desktop-client/e2e/page-models/mobile-navigation.ts index ad8f2bb13d1..6761d78e3c7 100644 --- a/packages/desktop-client/e2e/page-models/mobile-navigation.ts +++ b/packages/desktop-client/e2e/page-models/mobile-navigation.ts @@ -7,6 +7,22 @@ import { MobileReportsPage } from './mobile-reports-page'; import { MobileTransactionEntryPage } from './mobile-transaction-entry-page'; import { SettingsPage } from './settings-page'; +const NAVBAR_ROWS = 3; +const NAV_LINKS_HIDDEN_BY_DEFAULT = [ + 'Reports', + 'Schedules', + 'Payees', + 'Rules', + 'Settings', +]; +const ROUTES_BY_PAGE = { + Budget: '/budget', + Accounts: '/accounts', + Transactions: '/transactions/new', + Reports: '/reports', + Settings: '/settings', +}; + export class MobileNavigation { readonly page: Page; readonly heading: Locator; @@ -22,31 +38,23 @@ export class MobileNavigation { this.navbarSelector = '[role=navigation]'; } - static #NAVBAR_ROWS = 3; - static #NAV_LINKS_HIDDEN_BY_DEFAULT = [ - 'Reports', - 'Schedules', - 'Payees', - 'Rules', - 'Settings', - ]; - static #ROUTES_BY_PAGE = { - Budget: '/budget', - Accounts: '/accounts', - Transactions: '/transactions/new', - Reports: '/reports', - Settings: '/settings', - }; - async dragNavbarUp() { const mainContentBoundingBox = await this.page .locator(this.mainContentSelector) .boundingBox(); + if (!mainContentBoundingBox) { + throw new Error('Unable to get bounding box of main content.'); + } + const navbarBoundingBox = await this.page .locator(this.navbarSelector) .boundingBox(); + if (!navbarBoundingBox) { + throw new Error('Unable to get bounding box of navbar.'); + } + await this.page.dragAndDrop(this.navbarSelector, this.mainContentSelector, { sourcePosition: { x: 1, y: 0 }, targetPosition: { @@ -61,39 +69,47 @@ export class MobileNavigation { .locator(this.navbarSelector) .boundingBox(); + if (!boundingBox) { + throw new Error('Unable to get bounding box of navbar.'); + } + await this.page.dragAndDrop(this.navbarSelector, this.navbarSelector, { sourcePosition: { x: 1, y: 0 }, targetPosition: { x: 1, // Only scroll until bottom of screen i.e. bottom of first navbar row. - y: boundingBox.height / MobileNavigation.#NAVBAR_ROWS, + y: boundingBox.height / NAVBAR_ROWS, }, }); } - async hasNavbarState(...states) { + async hasNavbarState(...states: string[]) { if ((await this.navbar.count()) === 0) { // No navbar on page. return false; } const dataNavbarState = await this.navbar.getAttribute('data-navbar-state'); + if (!dataNavbarState) { + throw new Error('Navbar does not have data-navbar-state attribute.'); + } return states.includes(dataNavbarState); } - async navigateToPage(pageName, pageModelFactory) { + async navigateToPage( + pageName: keyof typeof ROUTES_BY_PAGE, + pageModelFactory: () => T, + ): Promise { const pageInstance = pageModelFactory(); - if (this.page.url().endsWith(MobileNavigation.#ROUTES_BY_PAGE[pageName])) { + if (this.page.url().endsWith(ROUTES_BY_PAGE[pageName])) { // Already on the page. return pageInstance; } await this.navbar.waitFor(); - const navbarStates = MobileNavigation.#NAV_LINKS_HIDDEN_BY_DEFAULT.includes( - pageName, - ) + const navbarStates = NAV_LINKS_HIDDEN_BY_DEFAULT.includes(pageName) ? ['default', 'hidden'] : ['hidden']; @@ -136,7 +152,7 @@ export class MobileNavigation { async goToTransactionEntryPage() { return await this.navigateToPage( - 'Transaction', + 'Transactions', () => new MobileTransactionEntryPage(this.page), ); } diff --git a/packages/desktop-client/e2e/page-models/mobile-reports-page.ts b/packages/desktop-client/e2e/page-models/mobile-reports-page.ts index d76bc89daae..74b5684426c 100644 --- a/packages/desktop-client/e2e/page-models/mobile-reports-page.ts +++ b/packages/desktop-client/e2e/page-models/mobile-reports-page.ts @@ -10,7 +10,7 @@ export class MobileReportsPage { this.overview = page.getByTestId('reports-overview'); } - async waitFor(options) { - await this.overview.waitFor(options); + async waitFor(...options: Parameters) { + await this.overview.waitFor(...options); } } diff --git a/packages/desktop-client/e2e/page-models/mobile-transaction-entry-page.ts b/packages/desktop-client/e2e/page-models/mobile-transaction-entry-page.ts index 21d8a69ce0b..8937fd0d187 100644 --- a/packages/desktop-client/e2e/page-models/mobile-transaction-entry-page.ts +++ b/packages/desktop-client/e2e/page-models/mobile-transaction-entry-page.ts @@ -6,7 +6,6 @@ export class MobileTransactionEntryPage { readonly page: Page; readonly header: Locator; readonly amountField: Locator; - readonly add: Locator; readonly transactionForm: Locator; readonly footer: Locator; readonly addTransactionButton: Locator; @@ -22,8 +21,8 @@ export class MobileTransactionEntryPage { }); } - async waitFor(options) { - await this.transactionForm.waitFor(options); + async waitFor(...options: Parameters) { + await this.transactionForm.waitFor(...options); } async fillField(fieldLocator: Locator, content: string) { diff --git a/packages/desktop-client/e2e/page-models/settings-page.ts b/packages/desktop-client/e2e/page-models/settings-page.ts index 1a11c908a16..42c1f329191 100644 --- a/packages/desktop-client/e2e/page-models/settings-page.ts +++ b/packages/desktop-client/e2e/page-models/settings-page.ts @@ -24,24 +24,24 @@ export class SettingsPage { ); } - async waitFor(options) { - await this.settings.waitFor(options); + async waitFor(...options: Parameters) { + await this.settings.waitFor(...options); } async exportData() { await this.exportDataButton.click(); } - async useBudgetType(budgetType) { + async useBudgetType(budgetType: 'Envelope' | 'Tracking') { await this.switchBudgetTypeButton.waitFor(); const buttonText = await this.switchBudgetTypeButton.textContent(); - if (buttonText.includes(budgetType.toLowerCase())) { + if (buttonText?.includes(budgetType.toLowerCase())) { await this.switchBudgetTypeButton.click(); } } - async enableExperimentalFeature(featureName) { + async enableExperimentalFeature(featureName: string) { if (await this.advancedSettingsButton.isVisible()) { await this.advancedSettingsButton.click(); }