From 25c2c98cbc900d26854c776a676c1d14bca1c719 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Mon, 28 Mar 2022 00:38:19 +0100 Subject: [PATCH] Clean up page objects (#63) When we first started this project we were also new to Cypress. We added an initial implementation of page objects and got some basic scenarios working. Since then our understanding Cypress has improved plus our newest page objects are not consistent with how we did the original ones. We've also brought the concept of menus in. Finally, we're using our page objects inconsistently. Some scenarios depend on them. Others like the legacy tests ignore them completely. Updating the scenarios to be consistent is for another change. But this one focuses on getting the page objects sorted in readiness. We'll get them consistent and reduce some duplication by introducing some base page objects. ** Notes * Update deprecated use of cy.server() Cypress replaced this with `cy.intercept()` in its latest versions. Plus it's what we use elsewhere in suite. * Fix transactions feature cust reference We think the previous reference was just something we had found in all our AWS environments. We now use a reference that we know is included in our test import files. * Add a BasePage and extend all from it We start by creating a `BasePage` which we then extend all other pages from. It takes over duties for the `mainHeading()`. We also move to some conventions. - elements will feature their type in the name, for example, `cancelButton` - rather than refer to the main submit button on each page by its display name we go with one that is consistent. This also means we can declare it once in our `BasePage` - we expect all pages to provide a `confirm()` method. This will replace checks on `mainHeading()` littered throughout the test to confirm we're in the right place. Our convention now will be to ask the page object to confirm we're where we are supposed to be These changes mean a number of edits to the steps. Along the way whilst retesting we found some broken elements. So, scattered throughout the changes will be ones related to fixing a test rather than the switch to a base page. * Add BaseAppPage The base app page represents what you see once you have authenticated. This means the various menus. The intention is to access the menus through the page object that represents where you are rather than referencing menus separartly in the steps. We create a base class for it to remove duplication. We want to try and get referencing them out of the steps because we think it will help make them cleaner and clearer. * Add annual billing menu Whilst working on a scenario spotted that we were accessing the annual billing menu but had yet to wrap it in a 'menu'. * Move alert checking to a Cypress command Went with this option for the reasons set out in the method comments. Again, we're holding off touching the legacy tests and will deal with them separately. --- .../authorisation/authorisation_steps.js | 14 +-- cypress/integration/common/email.js | 92 ++++++++----------- cypress/integration/common/sign_in.js | 6 +- cypress/integration/common/transactions.js | 10 ++ .../email/expired_tokens/expired_tokens.js | 26 ++---- .../email/general/general_steps.js | 8 +- .../integration/export_data/export_data.js | 13 +-- cypress/integration/legacy/cfd/cfd_steps.js | 6 +- cypress/integration/legacy/pas/pas_steps.js | 6 +- cypress/integration/legacy/wml/wml_steps.js | 6 +- .../integration/review_annual_billing.feature | 16 ++-- .../annual_billing_steps.js | 18 ++-- cypress/integration/sign_in/sign_in_steps.js | 7 +- cypress/integration/transactions.feature | 4 +- .../transactions/transaction_steps.js | 19 +--- cypress/pages/accept_invite_page.js | 21 ++--- cypress/pages/add_user_page.js | 51 +++++----- .../pages/annual_billing_file_details_page.js | 13 ++- cypress/pages/annual_billing_page.js | 13 ++- cypress/pages/base_app_page.js | 32 +++++++ cypress/pages/base_page.js | 21 +++++ cypress/pages/change_password_page.js | 16 ++-- cypress/pages/edit_user_page.js | 53 +++++------ cypress/pages/export_data_page.js | 13 ++- cypress/pages/forgot_password_page.js | 17 ++-- cypress/pages/menus/annual_billing_menu.js | 23 +++++ cypress/pages/resend_unlock_page.js | 14 +-- cypress/pages/sign_in_page.js | 27 +++--- cypress/pages/transactions_page.js | 73 +++------------ cypress/pages/users_page.js | 25 ++--- cypress/plugins/index.js | 16 ++++ cypress/support/commands.js | 20 +++- 32 files changed, 368 insertions(+), 331 deletions(-) create mode 100644 cypress/integration/common/transactions.js create mode 100644 cypress/pages/base_app_page.js create mode 100644 cypress/pages/base_page.js create mode 100644 cypress/pages/menus/annual_billing_menu.js diff --git a/cypress/integration/authorisation/authorisation_steps.js b/cypress/integration/authorisation/authorisation_steps.js index c07a39e..27395e5 100644 --- a/cypress/integration/authorisation/authorisation_steps.js +++ b/cypress/integration/authorisation/authorisation_steps.js @@ -2,27 +2,27 @@ import { And, When, Then, But } from 'cypress-cucumber-preprocessor/steps' import TransactionsPage from '../../pages/transactions_page' When('I see the transactions page', () => { - TransactionsPage.mainHeading().contains('Transactions to be billed') + TransactionsPage.confirm() }) Then('I should see the admin menu', () => { - TransactionsPage.adminMenu().should('be.visible') + TransactionsPage.adminMenu.menuLink().should('be.visible') }) But('I should not see the admin menu', () => { - TransactionsPage.adminMenu().should('not.exist') + TransactionsPage.adminMenu.menuLink().should('not.exist') }) Then('I should see the billing menu', () => { - TransactionsPage.billingMenu().should('be.visible') + TransactionsPage.annualBillingMenu.menuLink().should('be.visible') }) But('I should not see the billing menu', () => { - TransactionsPage.billingMenu().should('not.exist') + TransactionsPage.annualBillingMenu.menuLink().should('not.exist') }) Then('I should see the transactions menu', () => { - TransactionsPage.transactionMenu().should('be.visible') + TransactionsPage.transactionsMenu.menuLink().should('be.visible') }) And('I should see download transactions', () => { @@ -35,5 +35,5 @@ But('I should not see download transactions', () => { Then('I should only see the {string} regime', (regime) => { cy.get('.navbar-text').contains(regime, { matchCase: false }) - TransactionsPage.regimeMenu().should('not.exist') + TransactionsPage.regimeMenu.menuLink().should('not.exist') }) diff --git a/cypress/integration/common/email.js b/cypress/integration/common/email.js index 8b7f1eb..84dcf24 100644 --- a/cypress/integration/common/email.js +++ b/cypress/integration/common/email.js @@ -6,9 +6,9 @@ import AddUserPage from '../../pages/add_user_page' import EditUserPage from '../../pages/edit_user_page' import ForgotPasswordPage from '../../pages/forgot_password_page' import LastEmailPage from '../../pages/last_email_page' -import MainMenu from '../../pages/menus/main_menu' import ResendUnlockPage from '../../pages/resend_unlock_page' import SignInPage from '../../pages/sign_in_page' +import TransactionsPage from '../../pages/transactions_page' import UsersPage from '../../pages/users_page' Given('I am a new user', () => { @@ -32,21 +32,19 @@ Given('I am an existing user', () => { When('a new account is created for me', () => { cy.signIn(Cypress.config().users.admin.email) - MainMenu.admin.getOption('User Management', '').click() + TransactionsPage.adminMenu.getOption('User Management', '').click() - UsersPage.addUserAccount().click() + UsersPage.addUserAccountButton().click() cy.get('@user').then((user) => { - AddUserPage.email().type(user.email) - AddUserPage.firstName().type(user.firstName) - AddUserPage.lastName().type(user.lastName) + AddUserPage.emailInput().type(user.email) + AddUserPage.firstNameInput().type(user.firstName) + AddUserPage.lastNameInput().type(user.lastName) - AddUserPage.regimeAccess('Waste').click() - AddUserPage.addAndInviteUser().click() - - cy.get('.col > .alert') - .should('contain.text', 'User account created') + AddUserPage.regimeAccessCheckbox('Waste').click() + AddUserPage.submitButton().click() }) + cy.alertShouldContain('User account created') }) And('I accept the invitation', () => { @@ -59,11 +57,11 @@ And('I accept the invitation', () => { const link = LastEmailPage.extractInvitationLink(lastEmail.last_email.body) cy.visit(link).then(() => { - AcceptInvitePage.mainHeading().should('contain', 'Set a password') + AcceptInvitePage.confirm() - AcceptInvitePage.password().type(Cypress.env('PASSWORD'), { log: false }) - AcceptInvitePage.passwordConfirmation().type(Cypress.env('PASSWORD'), { log: false }) - AcceptInvitePage.setPassword().click() + AcceptInvitePage.passwordInput().type(Cypress.env('PASSWORD'), { log: false }) + AcceptInvitePage.passwordConfirmationInput().type(Cypress.env('PASSWORD'), { log: false }) + AcceptInvitePage.submitButton().click() }) }) }) @@ -72,19 +70,19 @@ And('I accept the invitation', () => { Then('I will be signed in with my new account', () => { cy.get('@user').then((user) => { const username = `${user.firstName} ${user.lastName}` - MainMenu.user.menuLink().should('contain', username) + TransactionsPage.userMenu.menuLink().should('contain', username) }) }) And('I incorrectly enter my password 5 times', () => { cy.get('@user').then((user) => { - SignInPage.email().type(user.email) + SignInPage.emailInput().type(user.email) for (let i = 0; i < 5; i++) { - SignInPage.password().clear() - SignInPage.password().type(generateStringHelper()) + SignInPage.passwordInput().clear() + SignInPage.passwordInput().type(generateStringHelper()) - SignInPage.logIn().click() + SignInPage.submitButton().click() } }) }) @@ -93,18 +91,16 @@ And('I have forgotten my password', () => { SignInPage.visit() SignInPage.forgotPasswordLink().click() - ForgotPasswordPage.mainHeading().should('contain', 'Forgot your password?') + ForgotPasswordPage.confirm() cy.get('@user').then((user) => { - ForgotPasswordPage.email().type(user.email) - ForgotPasswordPage.sendMeResetPasswordInstructions().click() + ForgotPasswordPage.emailInput().type(user.email) + ForgotPasswordPage.submitButton().click() }) - cy.get('.col > .alert') - .should( - 'contain.text', - 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.' - ) + cy.alertShouldContain( + 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.' + ) }) When('I follow the link to unlock my account', () => { @@ -125,17 +121,15 @@ And('request another unlock email', () => { ResendUnlockPage.confirm() cy.get('@user').then((user) => { - ResendUnlockPage.email().type(user.email) - ResendUnlockPage.resendUnlockInstructions().click() + ResendUnlockPage.emailInput().type(user.email) + ResendUnlockPage.submitButton().click() }) }) Then('I will see confirmation my account is unlocked', () => { - cy.get('.col > .alert') - .should( - 'contain.text', - 'Your account has been unlocked successfully. Please sign in to continue.' - ) + cy.alertShouldContain( + 'Your account has been unlocked successfully. Please sign in to continue.' + ) }) But('I miss the first invitation email', () => { @@ -153,7 +147,7 @@ But('I miss the first invitation email', () => { But('I miss the first unlock email', () => { cy.get('@user').then((user) => { - LastEmailPage.lastEmail([user.email, 'Your account has been locked']) + LastEmailPage.lastEmail([`Hello ${user.firstName} ${user.lastName}`, 'account has been locked']) cy.get('@lastEmail').then((lastEmail) => { const link = LastEmailPage.extractUnlockAccountLink(lastEmail.last_email.body) @@ -164,18 +158,18 @@ But('I miss the first unlock email', () => { }) And('request another invitation email', () => { - MainMenu.admin.getOption('User Management', '').click() + TransactionsPage.adminMenu.getOption('User Management', '').click() cy.get('@user').then((user) => { - UsersPage.searchName().type(user.lastName) - UsersPage.search().click() + UsersPage.searchNameInput().type(user.lastName) + UsersPage.submitButton().click() - UsersPage.searchResults().each((element, index) => { + UsersPage.searchResultsTable().each((element, index) => { cy.get(`.table-responsive > tbody > tr:nth-child(${index + 1}) td`).eq(2).invoke('text').then((email) => { if (email === user.email) { cy.get(`.table-responsive > tbody > tr:nth-child(${index + 1})`).invoke('attr', 'id').then((id) => { - UsersPage.searchResultEdit(id).click() - EditUserPage.resendInvite().click() + UsersPage.searchResultEditButton(id).click() + EditUserPage.resendInviteButton().click() }) } }) @@ -184,17 +178,11 @@ And('request another invitation email', () => { }) Then('the TCM will confirm the user has been reinvited', () => { - cy.get('.col > .alert') - .should( - 'contain.text', - 'User reinvited' - ) + cy.alertShouldContain('User reinvited') }) Then('I will see confirmation an unlock email has been sent', () => { - cy.get('.col > .alert') - .should( - 'contain.text', - 'If your account exists, you will receive an email with instructions for how to unlock it in a few minutes.' - ) + cy.alertShouldContain( + 'If your account exists, you will receive an email with instructions for how to unlock it in a few minutes.' + ) }) diff --git a/cypress/integration/common/sign_in.js b/cypress/integration/common/sign_in.js index e01d7c8..ca5b53f 100644 --- a/cypress/integration/common/sign_in.js +++ b/cypress/integration/common/sign_in.js @@ -3,8 +3,8 @@ import SignInPage from '../../pages/sign_in_page' Given('I sign in as the {string} user', (user) => { SignInPage.visit() - SignInPage.email().type(Cypress.config().users[user].email) - SignInPage.password().type(Cypress.env('PASSWORD')) + SignInPage.emailInput().type(Cypress.config().users[user].email) + SignInPage.passwordInput().type(Cypress.env('PASSWORD')) - SignInPage.logIn().click() + SignInPage.submitButton().click() }) diff --git a/cypress/integration/common/transactions.js b/cypress/integration/common/transactions.js new file mode 100644 index 0000000..9640cd7 --- /dev/null +++ b/cypress/integration/common/transactions.js @@ -0,0 +1,10 @@ +import { When } from 'cypress-cucumber-preprocessor/steps' +import TransactionsPage from '../../pages/transactions_page' + +When('I select the {string} regime', (regimeName) => { + cy.task('regime', regimeName).then((regime) => { + cy.wrap(regime).as('regime') + + TransactionsPage.regimeMenu.getOption(regime.name).click() + }) +}) diff --git a/cypress/integration/email/expired_tokens/expired_tokens.js b/cypress/integration/email/expired_tokens/expired_tokens.js index 43ceff1..cf24e70 100644 --- a/cypress/integration/email/expired_tokens/expired_tokens.js +++ b/cypress/integration/email/expired_tokens/expired_tokens.js @@ -22,18 +22,16 @@ And('request another reset password email', () => { SignInPage.visit() SignInPage.forgotPasswordLink().click() - ForgotPasswordPage.mainHeading().should('contain', 'Forgot your password?') + ForgotPasswordPage.confirm() cy.get('@user').then((user) => { - ForgotPasswordPage.email().type(user.email) - ForgotPasswordPage.sendMeResetPasswordInstructions().click() + ForgotPasswordPage.emailInput().type(user.email) + ForgotPasswordPage.submitButton().click() }) - cy.get('.col > .alert') - .should( - 'contain.text', - 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.' - ) + cy.alertShouldContain( + 'If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.' + ) }) And('I try to accept the first invitation email', () => { @@ -55,20 +53,16 @@ When('I try to accept the first reset password email', () => { cy.visit(firstLink).then(() => { ChangePasswordPage.confirm() - ChangePasswordPage.password().type(Cypress.env('PASSWORD'), { log: false }) - ChangePasswordPage.passwordConfirmation().type(Cypress.env('PASSWORD'), { log: false }) - ChangePasswordPage.changeMyPassword().click() + ChangePasswordPage.passwordInput().type(Cypress.env('PASSWORD'), { log: false }) + ChangePasswordPage.passwordConfirmationInput().type(Cypress.env('PASSWORD'), { log: false }) + ChangePasswordPage.submitButton().click() }) }) }) Then('the TCM will confuse me and not be helpful', () => { cy.log('User redirected to the sign in page with no message about token being invalid') - cy.get('.col > .alert') - .should( - 'contain.text', - 'You need to sign in before continuing' - ) + cy.alertShouldContain('You need to sign in before continuing') cy.url().should('include', '/auth/sign_in') }) diff --git a/cypress/integration/email/general/general_steps.js b/cypress/integration/email/general/general_steps.js index 6e7e912..54ca6f5 100644 --- a/cypress/integration/email/general/general_steps.js +++ b/cypress/integration/email/general/general_steps.js @@ -11,11 +11,11 @@ When('I follow the link to reset my password', () => { const link = LastEmailPage.extractResetPasswordLink(lastEmail.last_email.body) cy.visit(link).then(() => { - ChangePasswordPage.mainHeading().should('contain', 'Change your password') + ChangePasswordPage.confirm() - ChangePasswordPage.password().type(Cypress.env('PASSWORD'), { log: false }) - ChangePasswordPage.passwordConfirmation().type(Cypress.env('PASSWORD'), { log: false }) - ChangePasswordPage.changeMyPassword().click() + ChangePasswordPage.passwordInput().type(Cypress.env('PASSWORD'), { log: false }) + ChangePasswordPage.passwordConfirmationInput().type(Cypress.env('PASSWORD'), { log: false }) + ChangePasswordPage.submitButton().click() }) }) }) diff --git a/cypress/integration/export_data/export_data.js b/cypress/integration/export_data/export_data.js index 9f6a46a..621c9ff 100644 --- a/cypress/integration/export_data/export_data.js +++ b/cypress/integration/export_data/export_data.js @@ -1,15 +1,9 @@ -import { When, Then, And } from 'cypress-cucumber-preprocessor/steps' +import { Then, And } from 'cypress-cucumber-preprocessor/steps' import ExportDataPage from '../../pages/export_data_page' import TransactionsPage from '../../pages/transactions_page' -When('I select the {string} regime', (regime) => { - TransactionsPage.regimeMenu().click() - TransactionsPage.regimeMenuItem(regime).click() -}) - And('I proceed to view file download details', () => { - TransactionsPage.transactionMenu().click() - TransactionsPage.downloadTransactionDataMenuItem().click() + TransactionsPage.transactionsMenu.getOption('Download Transaction Data').click() }) Then('I can view the Data Protection Notice', () => { @@ -17,6 +11,5 @@ Then('I can view the Data Protection Notice', () => { }) And('I can download transaction data', () => { - ExportDataPage.downloadBtn().should('have.attr', 'href', '/regimes/pas/data_export/download') - // DownloadTransactionFilePage.downloadBtn().click() + ExportDataPage.downloadButton().should('have.attr', 'href', '/regimes/pas/data_export/download') }) diff --git a/cypress/integration/legacy/cfd/cfd_steps.js b/cypress/integration/legacy/cfd/cfd_steps.js index 65a5d2c..67e103d 100644 --- a/cypress/integration/legacy/cfd/cfd_steps.js +++ b/cypress/integration/legacy/cfd/cfd_steps.js @@ -43,10 +43,10 @@ Before(() => { Given('I sign in as the {word} user', (regime) => { SignInPage.visit() - SignInPage.email().type(Cypress.config().users[regime].email) - SignInPage.password().type(Cypress.env('PASSWORD')) + SignInPage.emailInput().type(Cypress.config().users[regime].email) + SignInPage.passwordInput().type(Cypress.env('PASSWORD')) - SignInPage.logIn().click() + SignInPage.submitButton().click() }) Then('the user menu is visible', () => { diff --git a/cypress/integration/legacy/pas/pas_steps.js b/cypress/integration/legacy/pas/pas_steps.js index 3d2924d..3d34833 100644 --- a/cypress/integration/legacy/pas/pas_steps.js +++ b/cypress/integration/legacy/pas/pas_steps.js @@ -43,10 +43,10 @@ Before(() => { Given('I sign in as the {word} user', (regime) => { SignInPage.visit() - SignInPage.email().type(Cypress.config().users[regime].email) - SignInPage.password().type(Cypress.env('PASSWORD')) + SignInPage.emailInput().type(Cypress.config().users[regime].email) + SignInPage.passwordInput().type(Cypress.env('PASSWORD')) - SignInPage.logIn().click() + SignInPage.submitButton().click() }) Then('the user menu is visible', () => { diff --git a/cypress/integration/legacy/wml/wml_steps.js b/cypress/integration/legacy/wml/wml_steps.js index 401b76c..2f58c16 100644 --- a/cypress/integration/legacy/wml/wml_steps.js +++ b/cypress/integration/legacy/wml/wml_steps.js @@ -43,10 +43,10 @@ Before(() => { Given('I sign in as the {word} user', (regime) => { SignInPage.visit() - SignInPage.email().type(Cypress.config().users[regime].email) - SignInPage.password().type(Cypress.env('PASSWORD')) + SignInPage.emailInput().type(Cypress.config().users[regime].email) + SignInPage.passwordInput().type(Cypress.env('PASSWORD')) - SignInPage.logIn().click() + SignInPage.submitButton().click() }) Then('the user menu is visible', () => { diff --git a/cypress/integration/review_annual_billing.feature b/cypress/integration/review_annual_billing.feature index b65bb9a..8fb13ce 100644 --- a/cypress/integration/review_annual_billing.feature +++ b/cypress/integration/review_annual_billing.feature @@ -1,21 +1,19 @@ Feature: Review Annual Billing Background: - Given I sign in as the 'admin' user + Given I sign in as the 'admin' user When I select the 'Waste' regime - And I proceed to review Annual Billing details + And I proceed to review Annual Billing details Scenario: Review Annual Billing - Then I can view a list of Annual Billing Data Files - + Then I can view a list of Annual Billing Data Files Scenario: Review Annual Billing File Details - And I can view a list of Annual Billing Data Files - And I select an Annual Billing File to review + And I can view a list of Annual Billing Data Files + And I select an Annual Billing File to review Then I can view the details of the selected Annual Billing File Scenario: Navigate back to Review Annual Billing - And I can view a list of Annual Billing Data Files - And I select an Annual Billing File to review + And I can view a list of Annual Billing Data Files + And I select an Annual Billing File to review Then I can navigate back to Review Annual Billing - diff --git a/cypress/integration/review_annual_billing/annual_billing_steps.js b/cypress/integration/review_annual_billing/annual_billing_steps.js index 076f2b5..62f0167 100644 --- a/cypress/integration/review_annual_billing/annual_billing_steps.js +++ b/cypress/integration/review_annual_billing/annual_billing_steps.js @@ -1,17 +1,13 @@ -import { When, Then, And } from 'cypress-cucumber-preprocessor/steps' +import { Then, And } from 'cypress-cucumber-preprocessor/steps' import AnnualBillingPage from '../../pages/annual_billing_page' import AnnualBillingFileDetailsPage from '../../pages/annual_billing_file_details_page' import TransactionsPage from '../../pages/transactions_page' -When('I select the {string} regime', (regime) => { - TransactionsPage.regimeMenu().click() - TransactionsPage.regimeMenuItem(regime).click() -}) - And('I proceed to review Annual Billing details', () => { - TransactionsPage.annualBillingDataMenuItem().click() - TransactionsPage.reviewAnnualBillingDataMenuItem().click() - AnnualBillingPage.mainHeading().should('be.visible') + cy.get('@regime').then((regime) => { + TransactionsPage.annualBillingMenu.getOption('Review Annual Billing Data', regime.slug).click() + }) + AnnualBillingPage.confirm() }) Then('I can view a list of Annual Billing Data Files', () => { @@ -20,7 +16,7 @@ Then('I can view a list of Annual Billing Data Files', () => { And('I select an Annual Billing File to review', () => { AnnualBillingPage.fileNameLink().click() - AnnualBillingFileDetailsPage.mainHeading().should('be.visible') + AnnualBillingFileDetailsPage.confirm() }) Then('I can view the details of the selected Annual Billing File', () => { @@ -29,6 +25,6 @@ Then('I can view the details of the selected Annual Billing File', () => { }) And('I can navigate back to Review Annual Billing', () => { - AnnualBillingFileDetailsPage.backBtn().click() + AnnualBillingFileDetailsPage.backButton().click() AnnualBillingPage.mainHeading().should('be.visible') }) diff --git a/cypress/integration/sign_in/sign_in_steps.js b/cypress/integration/sign_in/sign_in_steps.js index 64f0846..d10b6b7 100644 --- a/cypress/integration/sign_in/sign_in_steps.js +++ b/cypress/integration/sign_in/sign_in_steps.js @@ -3,11 +3,12 @@ import SignInPage from '../../pages/sign_in_page' Given('I visit the sign in page', () => { SignInPage.visit() + SignInPage.confirm() }) When('I enter my credentials', () => { - SignInPage.email().type(Cypress.config().users.system.email) - SignInPage.password().type(Cypress.env('PASSWORD')) + SignInPage.emailInput().type(Cypress.config().users.system.email) + SignInPage.passwordInput().type(Cypress.env('PASSWORD')) - SignInPage.logIn().click() + SignInPage.submitButton().click() }) diff --git a/cypress/integration/transactions.feature b/cypress/integration/transactions.feature index 15f3c5e..b4d56f8 100644 --- a/cypress/integration/transactions.feature +++ b/cypress/integration/transactions.feature @@ -5,5 +5,5 @@ Feature: Transactions Scenario: Search for customer When I select the 'Water Quality' regime - And I search for the customer 'A60425822C' - Then I see only results for customer 'A60425822C' + And I search for the customer 'A61000001C' + Then I see only results for customer 'A61000001C' diff --git a/cypress/integration/transactions/transaction_steps.js b/cypress/integration/transactions/transaction_steps.js index 46593c8..89a9625 100644 --- a/cypress/integration/transactions/transaction_steps.js +++ b/cypress/integration/transactions/transaction_steps.js @@ -1,4 +1,4 @@ -import { Then, When, And, Before } from 'cypress-cucumber-preprocessor/steps' +import { Then, And, Before } from 'cypress-cucumber-preprocessor/steps' import TransactionsPage from '../../pages/transactions_page' Before(() => { @@ -17,23 +17,14 @@ Before(() => { // available throughout, and to keep the tests a little cleaner. // // https://docs.cypress.io/guides/guides/network-requests.html#Waiting - cy.server() - cy.route({ - method: 'GET', - url: '/regimes/cfd/transactions*' - }).as('getTransactions') -}) - -When('I select the {string} regime', (regime) => { - TransactionsPage.regimeMenu().click() - TransactionsPage.regimeMenuItem(regime).click() + cy.intercept('GET', '**/regimes/*/transactions?search=*').as('getSearch') }) And('I search for the customer {string}', (customer) => { - TransactionsPage.search().type(customer) - TransactionsPage.searchBtn().click() + TransactionsPage.searchInput().type(customer) + TransactionsPage.submitButton().click() - cy.wait('@getTransactions') + cy.wait('@getSearch') }) Then('I see only results for customer {string}', (customer) => { diff --git a/cypress/pages/accept_invite_page.js b/cypress/pages/accept_invite_page.js index 3308d27..842428b 100644 --- a/cypress/pages/accept_invite_page.js +++ b/cypress/pages/accept_invite_page.js @@ -1,23 +1,20 @@ -class AcceptInvitePage { - static visit (inviteLink) { - cy.visit(inviteLink) - } +import BasePage from './base_page' - static mainHeading () { - return cy.get('h2') +class AcceptInvitePage extends BasePage { + static confirm () { + cy.get('h1').should('contain', 'Set a password') + cy.url().should('include', '/auth/invitation/accept') } - static password () { + // Elements + + static passwordInput () { return cy.get('input#user_password') } - static passwordConfirmation () { + static passwordConfirmationInput () { return cy.get('input#user_password_confirmation') } - - static setPassword () { - return cy.get('input[name="commit"]') - } } export default AcceptInvitePage diff --git a/cypress/pages/add_user_page.js b/cypress/pages/add_user_page.js index 0f4d29d..5ac4c2c 100644 --- a/cypress/pages/add_user_page.js +++ b/cypress/pages/add_user_page.js @@ -1,36 +1,34 @@ -class AddUserPage { - static mainHeading () { - return cy.get('h1') - } +import BaseAppPage from './base_app_page' - static email () { - return cy.get('input#user_email') +class AddUserPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Add User Account') + cy.url().should('include', '/users/new') } - static firstName () { - return cy.get('input#user_first_name') + // elements + + static cancelButton () { + return cy.get('a.btn-secondary') } - static lastName () { - return cy.get('input#user_last_name') + static emailInput () { + return cy.get('input#user_email') } - static enabled () { + static enabledCheckbox () { return cy.get('input#user_enabled') } - static role (option) { - const options = { - 'Read-only User': 'input#role_read_only', - 'Read-only + Export': 'input#role_read_only_export', - 'Billing Admin': 'role_billing', - 'System Admin': 'role_admin' - } + static firstNameInput () { + return cy.get('input#user_first_name') + } - return cy.get(options[option]) + static lastNameInput () { + return cy.get('input#user_last_name') } - static regimeAccess (option) { + static regimeAccessCheckbox (option) { const options = { Installations: 'input#user_regime_users_attributes_0_enabled', Waste: 'input#user_regime_users_attributes_1_enabled', @@ -40,12 +38,15 @@ class AddUserPage { return cy.get(options[option]) } - static addAndInviteUser () { - return cy.get('input[name="commit"]') - } + static roleRadioButton (option) { + const options = { + 'Read-only User': 'input#role_read_only', + 'Read-only + Export': 'input#role_read_only_export', + 'Billing Admin': 'role_billing', + 'System Admin': 'role_admin' + } - static cancel () { - return cy.get('a.btn-secondary') + return cy.get(options[option]) } } diff --git a/cypress/pages/annual_billing_file_details_page.js b/cypress/pages/annual_billing_file_details_page.js index 26e203d..eced960 100644 --- a/cypress/pages/annual_billing_file_details_page.js +++ b/cypress/pages/annual_billing_file_details_page.js @@ -1,8 +1,13 @@ -class AnnualBillingFileDetailsPage { - static mainHeading () { - return cy.get('h1') +import BaseAppPage from './base_app_page' + +class AnnualBillingFileDetailsPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Annual Billing Data File Details') + cy.url().should('include', '/annual_billing_data_files') } + // Elements + static errorsHeading () { return cy.get('h2') } @@ -11,7 +16,7 @@ class AnnualBillingFileDetailsPage { return cy.get('.panel') } - static backBtn () { + static backButton () { return cy.get('.btn.btn-secondary') } } diff --git a/cypress/pages/annual_billing_page.js b/cypress/pages/annual_billing_page.js index 525ccca..9532bb0 100644 --- a/cypress/pages/annual_billing_page.js +++ b/cypress/pages/annual_billing_page.js @@ -1,14 +1,19 @@ -class AnnualBillingPage { - static mainHeading () { - return cy.get('h1') +import BaseAppPage from './base_app_page' + +class AnnualBillingPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Annual Billing Data Files') + cy.url().should('include', '/annual_billing_data_files') } + // Elements + static dataFilesTable () { return cy.get('.table') } static fileNameLink () { - return cy.get('.table > tbody > tr > td > [href="/regimes/wml/annual_billing_data_files/48"]') + return cy.get('.table > tbody > tr:nth-child(1) > td:nth-child(1)') } } diff --git a/cypress/pages/base_app_page.js b/cypress/pages/base_app_page.js new file mode 100644 index 0000000..d284bde --- /dev/null +++ b/cypress/pages/base_app_page.js @@ -0,0 +1,32 @@ +import AdminMenu from './menus/admin_menu' +import AnnualBillingMenu from './menus/annual_billing_menu' +import BasePage from './base_page' +import RegimeMenu from './menus/regime_menu' +import TransactionsMenu from './menus/transactions_menu' +import UserMenu from './menus/user_menu' + +class BaseAppPage extends BasePage { + // Menus + + static get adminMenu () { + return AdminMenu + } + + static get annualBillingMenu () { + return AnnualBillingMenu + } + + static get regimeMenu () { + return RegimeMenu + } + + static get transactionsMenu () { + return TransactionsMenu + } + + static get userMenu () { + return UserMenu + } +} + +export default BaseAppPage diff --git a/cypress/pages/base_page.js b/cypress/pages/base_page.js new file mode 100644 index 0000000..7fdac4f --- /dev/null +++ b/cypress/pages/base_page.js @@ -0,0 +1,21 @@ +class BasePage { + static confirm () { + throw new Error("Extending class must implement 'confirm()'") + } + + // Elements + + static appVersion () { + return cy.get('.app-version') + } + + static mainHeading () { + return cy.get('h1') + } + + static submitButton () { + return cy.get('input[name="commit"]') + } +} + +export default BasePage diff --git a/cypress/pages/change_password_page.js b/cypress/pages/change_password_page.js index bb91db7..268166b 100644 --- a/cypress/pages/change_password_page.js +++ b/cypress/pages/change_password_page.js @@ -1,24 +1,20 @@ -class ChangePasswordPage { +import BasePage from './base_page' + +class ChangePasswordPage extends BasePage { static confirm () { cy.get('h1').should('contain', 'Change your password') cy.url().should('include', '/auth/password') } - static mainHeading () { - return cy.get('h1') - } + // Elements - static password () { + static passwordInput () { return cy.get('input#user_password') } - static passwordConfirmation () { + static passwordConfirmationInput () { return cy.get('input#user_password_confirmation') } - - static changeMyPassword () { - return cy.get('input[name="commit"]') - } } export default ChangePasswordPage diff --git a/cypress/pages/edit_user_page.js b/cypress/pages/edit_user_page.js index ed9dea3..2872ebb 100644 --- a/cypress/pages/edit_user_page.js +++ b/cypress/pages/edit_user_page.js @@ -1,32 +1,30 @@ -class AddUserPage { - static mainHeading () { - return cy.get('h1') - } +import BaseAppPage from './base_app_page' - static firstName () { - return cy.get('input#user_first_name') +class EditUserPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Edit User Account') + cy.url().should('include', '/edit') } - static lastName () { - return cy.get('input#user_last_name') + // elements + + static cancelButton () { + return cy.get('a.btn-secondary') } - static enabled () { + static enabledCheckbox () { return cy.get('input#user_enabled') } - static role (option) { - const options = { - 'Read-only User': 'input#role_read_only', - 'Read-only + Export': 'input#role_read_only_export', - 'Billing Admin': 'role_billing', - 'System Admin': 'role_admin' - } + static firstNameInput () { + return cy.get('input#user_first_name') + } - return cy.get(options[option]) + static lastNameInput () { + return cy.get('input#user_last_name') } - static regimeAccess (option) { + static regimeAccessCheckbox (option) { const options = { Installations: 'input#user_regime_users_attributes_0_enabled', Waste: 'input#user_regime_users_attributes_1_enabled', @@ -36,17 +34,20 @@ class AddUserPage { return cy.get(options[option]) } - static updateUser () { - return cy.get('input[name="commit"]') - } - - static resendInvite () { + static resendInviteButton () { return cy.get('a.btn-warning') } - static cancel () { - return cy.get('a.btn-secondary') + static roleRadioButton (option) { + const options = { + 'Read-only User': 'input#role_read_only', + 'Read-only + Export': 'input#role_read_only_export', + 'Billing Admin': 'role_billing', + 'System Admin': 'role_admin' + } + + return cy.get(options[option]) } } -export default AddUserPage +export default EditUserPage diff --git a/cypress/pages/export_data_page.js b/cypress/pages/export_data_page.js index d3ff91b..c1698ff 100644 --- a/cypress/pages/export_data_page.js +++ b/cypress/pages/export_data_page.js @@ -1,13 +1,18 @@ -class ExportDataPage { - static mainHeading () { - return cy.get('h1') +import BaseAppPage from './base_app_page' + +class ExportDataPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Download Transaction Data') + cy.url().should('include', '/data_export') } + // Elements + static dataProtectionNotice () { return cy.get('h5') } - static downloadBtn () { + static downloadButton () { return cy.get('.btn.btn-primary') } } diff --git a/cypress/pages/forgot_password_page.js b/cypress/pages/forgot_password_page.js index 4bcc2fc..9d81657 100644 --- a/cypress/pages/forgot_password_page.js +++ b/cypress/pages/forgot_password_page.js @@ -1,14 +1,15 @@ -class ForgotPasswordPage { - static mainHeading () { - return cy.get('h1') - } +import BasePage from './base_page' - static email () { - return cy.get('input#user_email') +class ForgotPasswordPage extends BasePage { + static confirm () { + cy.get('h1').should('contain', 'Forgot your password?') + cy.url().should('include', '/auth/password/new') } - static sendMeResetPasswordInstructions () { - return cy.get('input[name=commit]') + // Elements + + static emailInput () { + return cy.get('input#user_email') } } diff --git a/cypress/pages/menus/annual_billing_menu.js b/cypress/pages/menus/annual_billing_menu.js new file mode 100644 index 0000000..cfe02a4 --- /dev/null +++ b/cypress/pages/menus/annual_billing_menu.js @@ -0,0 +1,23 @@ +class AnnualBillingMenu { + static get selector () { + return '[aria-labelledby="navbarAnnualBillingSelectorLink"]' + } + + static options (regimeSlug) { + return { + 'Review Annual Billing Data': `[href="/regimes/${regimeSlug}/annual_billing_data_files"]` + } + } + + static menuLink () { + return cy.get('#navbarAnnualBillingSelectorLink') + } + + static getOption (optionText, regimeSlug) { + this.menuLink().click() + + return cy.get(`${this.selector} > ${this.options(regimeSlug)[optionText]}`) + } +} + +export default AnnualBillingMenu diff --git a/cypress/pages/resend_unlock_page.js b/cypress/pages/resend_unlock_page.js index 41b7504..432514f 100644 --- a/cypress/pages/resend_unlock_page.js +++ b/cypress/pages/resend_unlock_page.js @@ -1,20 +1,16 @@ -class ResendUnlockPage { +import BasePage from './base_page' + +class ResendUnlockPage extends BasePage { static confirm () { cy.get('h1').should('contain', 'Resend unlock instructions') cy.url().should('include', '/auth/unlock') } - static mainHeading () { - return cy.get('h1') - } + // Elements - static email () { + static emailInput () { return cy.get('#user_email') } - - static resendUnlockInstructions () { - return cy.get('[name=commit]') - } } export default ResendUnlockPage diff --git a/cypress/pages/sign_in_page.js b/cypress/pages/sign_in_page.js index 33051b6..00c921a 100644 --- a/cypress/pages/sign_in_page.js +++ b/cypress/pages/sign_in_page.js @@ -1,28 +1,29 @@ -class SignInPage { - static visit () { - cy.visit('/auth/sign_in') - } +import BasePage from './base_page' - static mainHeading () { - return cy.get('h1') +class SignInPage extends BasePage { + static confirm () { + cy.get('h1').should('contain', 'Sign in') + cy.url().should('include', '/auth/sign_in') } - static email () { - return cy.get('#user_email') + static visit () { + cy.visit('/auth/sign_in') } - static password () { - return cy.get('#user_password') - } + // Elements - static logIn () { - return cy.get('[name=commit]') + static emailInput () { + return cy.get('#user_email') } static forgotPasswordLink () { return cy.get('a[href="/auth/password/new"]') } + static passwordInput () { + return cy.get('#user_password') + } + static resendUnlockLink () { return cy.get('a[href="/auth/unlock/new"]') } diff --git a/cypress/pages/transactions_page.js b/cypress/pages/transactions_page.js index 6c1b1a8..8300444 100644 --- a/cypress/pages/transactions_page.js +++ b/cypress/pages/transactions_page.js @@ -1,22 +1,23 @@ -class TransactionsPage { +import BaseAppPage from './base_app_page' + +class TransactionsPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Transactions to be billed') + // As transactions is the root path when authenticated we can't guarantee we'll have a url to assert against. + // This is why we don't have an assertion against the url like other page objects + } + static visit () { - // Transactions is the root path. You just have to be authenticated - // to see it + // Transactions is the root path. You just have to be authenticated to see it cy.visit('/') } - static mainHeading () { - return cy.get('h1') - } + // Elements - static search () { + static searchInput () { return cy.get('#search') } - static searchBtn () { - return cy.get('[name=commit]') - } - static resultsTable () { return cy.get('.table') } @@ -24,56 +25,6 @@ class TransactionsPage { static customerColumn () { return cy.get('.table > tbody > tr > td:nth-child(4)') } - - static transactionMenu () { - return cy.get('#navbarTransactionsSelectorLink') - } - - static adminMenu () { - return cy.get('#navbarAdminSelectorLink') - } - - static billingMenu () { - return cy.get('#navbarAnnualBillingSelectorLink') - } - - static regimeMenu () { - return cy.get('#navbarRegimeSelectorLink') - } - - static regimeMenuItem (regime) { - let slug - - switch (regime) { - case 'Water Quality': - slug = 'cfd' - break - case 'Installations': - slug = 'pas' - break - case 'Waste': - slug = 'wml' - break - } - - return cy.get(`.nav-item.show > .dropdown-menu > [href="/regimes/${slug}/transactions"]`) - } - - static downloadTransactionDataMenuItem () { - return cy.get(':nth-child(1) > .nav-item > .dropdown-menu > [href="/regimes/pas/data_export"]') - } - - static annualBillingDataMenuItem () { - return cy.get('#navbarAnnualBillingSelectorLink') - } - - static reviewAnnualBillingDataMenuItem () { - return cy.get(':nth-child(1) > .dropdown > div > [href="/regimes/wml/annual_billing_data_files"]') - } - - static regionDropDown () { - return cy.get('#region') - } } export default TransactionsPage diff --git a/cypress/pages/users_page.js b/cypress/pages/users_page.js index 51d91c3..fee5fe9 100644 --- a/cypress/pages/users_page.js +++ b/cypress/pages/users_page.js @@ -1,26 +1,27 @@ -class UsersPage { - static mainHeading () { - return cy.get('h1') +import BaseAppPage from './base_app_page' + +class UsersPage extends BaseAppPage { + static confirm () { + cy.get('h1').should('contain', 'Users') + cy.url().should('include', '/users') } - static addUserAccount () { + // Elements + + static addUserAccountButton () { return cy.get('button#new-user') } - static searchName () { + static searchNameInput () { return cy.get('input#search[type="search"]') } - static searchResults () { - return cy.get('table.table-responsive > tbody > tr') - } - - static searchResultEdit (id) { + static searchResultEditButton (id) { return cy.get(`a.btn-success[href="/users/${id}/edit"]`) } - static search () { - return cy.get('[name=commit]') + static searchResultsTable () { + return cy.get('table.table-responsive > tbody > tr') } } diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 79714cc..fce9692 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -125,6 +125,22 @@ module.exports = (on, config) => { reject(error) }) }) + }, + + regime (identifier) { + const regimes = { + pas: 'Installations', + wml: 'Waste', + cfd: 'Water Quality' + } + let result + for (const [key, value] of Object.entries(regimes)) { + if (key === identifier.toLowerCase() || value.toLowerCase() === identifier.toLowerCase()) { + result = { slug: key, name: value } + } + } + + return result } }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index f696cc3..6bf78b2 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -33,9 +33,9 @@ import SignInPage from '../pages/sign_in_page' */ Cypress.Commands.add('signIn', (email, password = Cypress.env('PASSWORD')) => { SignInPage.visit() - SignInPage.email().type(email) - SignInPage.password().type(password, { log: false }) - SignInPage.logIn().click() + SignInPage.emailInput().type(email) + SignInPage.passwordInput().type(password, { log: false }) + SignInPage.submitButton().click() }) /** @@ -119,3 +119,17 @@ Cypress.Commands.add('addUser', (user) => { }) }) }) + +/** + * Use when you want to check an alert contains a certain message + * + * An action will often lead to alert appearing that is shown on the page after the one we have been interacting with. + * To avoid importing the landing page into steps just to check its alert i.e. if we'd added this method to BasePage.js, + * we instead add it as a custom command. + * + * It means we can check the alert without first having to declare the page we are on. But having it as a command means + * we can define in just one place what the selector for our alerts is. + */ +Cypress.Commands.add('alertShouldContain', (text) => { + cy.get('.col > .alert').should('contain.text', text) +})