Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLR E2E Updates #11389

Merged
merged 36 commits into from Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ccdefe4
Update Overlay page to use submit function
Jul 21, 2023
38716a6
Bring down wait times in cypress tests
Jul 24, 2023
96539ff
Attempt revamp of saving entities in modal overlays
Jul 24, 2023
e3a8fac
Try turning off autosave in overlay
Jul 24, 2023
0dcf00f
Add timeout on authentication
Jul 24, 2023
20e584f
Try clearing session between logins
Jul 24, 2023
33982f9
Remove clear session
Jul 25, 2023
cb8f218
Remove clear session
Jul 25, 2023
56c0473
Remove Timeout
Jul 25, 2023
911a858
Update node version for e2e tests to use nvmrc
Jul 25, 2023
ff7a030
Update to setupnodev3
Jul 25, 2023
c2a60dc
Remove findByText for get and find in form stepdefs
Jul 25, 2023
18d3b57
Remove deprecated xpath and get rid of waitUntil in cypress
Jul 25, 2023
067d253
Update deploy for E2E tests
Jul 25, 2023
0273f76
Swap checkout in e2e test
Jul 25, 2023
564dd54
Remove node setup for e2e tests
Jul 25, 2023
5121172
Fix accessibility deployment
Jul 25, 2023
f960d1d
Add timeout to every get, wait, visit, find call
Jul 26, 2023
0fb9f7e
Remove xpath and waituntil dependencies
Jul 26, 2023
9dcfa0e
Swap out invoke function for get
Jul 26, 2023
af7c702
Reset default timeout to 4 seconds
Jul 26, 2023
7bff752
Remove individual timeouts on each cypress action
Jul 26, 2023
66803b9
Swap table button calls to get method
Jul 26, 2023
bcf6a0d
Add wait swapping between admin and user
Jul 26, 2023
9a32ebe
Turn back on autosave
Jul 27, 2023
c777343
Slightly longer wait on waiting for release
Jul 27, 2023
385246d
Slightly longer wait before setting login to make sure auth call goes…
Jul 27, 2023
7dd49ca
Add tests for submitting for the modaloverlaypage
Jul 27, 2023
6c80116
Add test for entering and closing the overlay for modaloverlay
Jul 27, 2023
a4e7175
Fix footer showing previous on first page and nowhere on mlr
Jul 28, 2023
8d588c3
Fix missing text on MLR Reporting page
Jul 29, 2023
82b20d5
Move EntityProvider to index for neater import
Jul 29, 2023
0ca73e1
Overhaul entire MLR Reporting Page
Jul 29, 2023
ef347c2
Fix up testing definitions
Jul 29, 2023
87867ef
PR cleanup
Jul 29, 2023
46995ad
Add back mobile/tablet view for modal overlay entities
Jul 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/deploy.yml
Expand Up @@ -22,12 +22,9 @@ jobs:
run: ./.github/build-vars.sh set_values
env:
CODE_CLIMATE_ID: ${{ secrets.CODE_CLIMATE_ID }}
- name: read .nvmrc
id: node_version
run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the deploy updates were an attempt to get Cypress to use the same node version we use. Unfortunately, Cypress doesn't respect the node version you give it. So setting up node doesn't matter. It seems its tied to the git actions node version. Heres a thread with all sorts of other threads to dig down the rabbit hole (cypress-io/github-action#637).

That being said, we were on v1 which was no longer supported, so swapping to v3 here is a solid upgrade.

with:
node-version: ${{ steps.node_version.outputs.NODE_VERSION }}
node-version-file: ".nvmrc"
- uses: actions/cache@v2
with:
path: |
Expand Down Expand Up @@ -89,12 +86,9 @@ jobs:
run: ./.github/github-lock.sh $branch_name
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: read .nvmrc
id: node_version
run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc)
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ${{ steps.node_version.outputs.NODE_VERSION }}
node-version-file: ".nvmrc"
- uses: actions/cache@v2
with:
path: |
Expand Down Expand Up @@ -131,9 +125,11 @@ jobs:
if: ${{ github.ref != 'refs/heads/production' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v3

- name: Run Cypress Tests
uses: cypress-io/github-action@v4.2.0
uses: cypress-io/github-action@v5
Comment on lines +128 to +132
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about attempting to get cypress to use our node version

with:
working-directory: tests/cypress
spec: |
Expand All @@ -148,6 +144,7 @@ jobs:
CYPRESS_STATE_USER_PASSWORD: ${{ secrets.CYPRESS_STATE_USER_PASSWORD }}
CYPRESS_ADMIN_USER_EMAIL: ${{ secrets.CYPRESS_ADMIN_USER_EMAIL }}
CYPRESS_ADMIN_USER_PASSWORD: ${{ secrets.CYPRESS_ADMIN_USER_PASSWORD }}

- name: Upload screenshots
uses: actions/upload-artifact@v2
if: failure()
Expand All @@ -163,9 +160,11 @@ jobs:
if: ${{ github.ref != 'refs/heads/production' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v3

- name: Check Project A11y
uses: cypress-io/github-action@v4.2.0
uses: cypress-io/github-action@v5
with:
working-directory: tests/cypress
spec: tests/accessibility/*.feature
Expand All @@ -179,6 +178,7 @@ jobs:
CYPRESS_ADMIN_USER_EMAIL: ${{ secrets.CYPRESS_ADMIN_USER_EMAIL }}
CYPRESS_ADMIN_USER_PASSWORD: ${{ secrets.CYPRESS_ADMIN_USER_PASSWORD }}
RUN_PA11Y: true

- name: Upload screenshots
uses: actions/upload-artifact@v2
if: failure()
Expand Down
100 changes: 60 additions & 40 deletions services/ui-src/src/components/overlays/EntityDetailsOverlay.tsx
Expand Up @@ -12,7 +12,13 @@ import {
isFieldElement,
ReportStatus,
} from "types";
import { filterFormData, useUser } from "utils";
import {
entityWasUpdated,
filterFormData,
getEntriesToClear,
setClearedEntriesToDefaultValue,
useUser,
} from "utils";
import accordionVerbiage from "../../verbiage/pages/accordion";
import overlayVerbiage from "../../verbiage/pages/overlays";
import { EntityContext } from "components/reports/EntityProvider";
Expand All @@ -29,13 +35,8 @@ export const EntityDetailsOverlay = ({
const { report, updateReport } = useContext(ReportContext);
const { full_name, state } = useUser().user ?? {};
const onError = () => {};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be noted that this line in particular was causing huge errors we had no insight into. It was being passed into the form and hiding validation errors. I've removed the setting of a blank error handler and the validation errors we've been seeing should clear up.

const {
entities,
updateEntities,
setEntities,
setSelectedEntity,
setEntityType,
} = useContext(EntityContext);
const { setEntities, setSelectedEntity, setEntityType } =
useContext(EntityContext);

useEffect(() => {
setSelectedEntity(selectedEntity);
Expand All @@ -49,33 +50,56 @@ export const EntityDetailsOverlay = ({
};
}, [entityType, selectedEntity]);

const reportFieldDataEntities = report?.fieldData[entityType] || [];

const onSubmit = async (enteredData: AnyObject) => {
setSubmitting(true);
const filteredFormData = filterFormData(
enteredData,
form.fields.filter(isFieldElement)
);
const newEntity = {
...selectedEntity,
...filteredFormData,
};
updateEntities(newEntity);
const reportKeys = {
reportType: report?.reportType,
state: state,
id: report?.id,
};
const dataToWrite = {
metadata: {
status: ReportStatus.IN_PROGRESS,
lastAlteredBy: full_name,
},
fieldData: {
program: entities,
},
};
await updateReport(reportKeys, dataToWrite);
setSubmitting(false);
if (userIsEndUser) {
setSubmitting(true);
const reportKeys = {
reportType: report?.reportType,
state: state,
id: report?.id,
};
const currentEntities = [...(report?.fieldData[entityType] || [])];
const selectedEntityIndex = report?.fieldData[entityType].findIndex(
(entity: EntityShape) => entity.id === selectedEntity?.id
);
const filteredFormData = filterFormData(
enteredData,
form.fields.filter(isFieldElement)
);
const entriesToClear = getEntriesToClear(
enteredData,
form.fields.filter(isFieldElement)
);
const newEntity = {
...selectedEntity,
...filteredFormData,
};
let newEntities = currentEntities;
newEntities[selectedEntityIndex] = newEntity;
newEntities[selectedEntityIndex] = setClearedEntriesToDefaultValue(
newEntities[selectedEntityIndex],
entriesToClear
);
const shouldSave = entityWasUpdated(
reportFieldDataEntities[selectedEntityIndex],
newEntity
);
if (shouldSave) {
const dataToWrite = {
metadata: {
status: ReportStatus.IN_PROGRESS,
lastAlteredBy: full_name,
},
fieldData: {
[entityType]: newEntities,
},
};
await updateReport(reportKeys, dataToWrite);
}
setSubmitting(false);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alllllll this code was updated because, it turns out, the submit button wasn't hooked up to anything. The MLR Overlay was entirely reliant on autosave, and we found that if a user goes quick enough the autosave call will get cancelled when leaving the overlay page and their last item wouldn't save. This would cause a validation error and give us an error about the submit button being disabled. All this code, plus the <Button type="submit" sx={sx.saveButton} form={form.id}> code just underneath this is to make sure the save and close button saves and closes for real.

closeEntityDetailsOverlay();
setSidebarHidden(false);
};
Expand Down Expand Up @@ -140,15 +164,11 @@ export const EntityDetailsOverlay = ({
autosave={true}
disabled={!userIsEndUser}
validateOnRender={validateOnRender || false}
dontReset={false}
dontReset={true}
/>
<Box sx={sx.footerBox}>
<Flex sx={sx.buttonFlex}>
<Button
onClick={() => closeOverlay()}
type="submit"
sx={sx.saveButton}
>
<Button type="submit" sx={sx.saveButton} form={form.id}>
{submitting ? (
<Spinner size="md" />
) : report?.locked ? (
Expand Down
1 change: 0 additions & 1 deletion tests/cypress/cypress.config.js
Expand Up @@ -12,7 +12,6 @@ module.exports = defineConfig({
screenshotsFolder: "screenshots",
videosFolder: "videos",
downloadsFolder: "downloads",
defaultCommandTimeout: 2000000000,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the 2 billion millisecond timeout that was causing prs to spin endlessly.

types: ["cypress", "cypress-axe"],
env: {
STATE_USER_EMAIL: "cypressstateuser@test.com",
Expand Down
4 changes: 1 addition & 3 deletions tests/cypress/package.json
Expand Up @@ -14,14 +14,12 @@
"devDependencies": {
"@badeball/cypress-cucumber-preprocessor": "^15.1.3",
"@cypress-audit/pa11y": "^1.3.1",
"@cypress/xpath": "^2.0.3",
"@testing-library/cypress": "^9.0.0",
"axe-core": "^4.6.3",
"concurrently": "^7.6.0",
"cypress": "^12.5.1",
"cypress-axe": "^1.3.0",
"cypress-file-upload": "^5.0.8",
"cypress-wait-until": "^1.7.2"
"cypress-file-upload": "^5.0.8"
},
"cypress-cucumber-preprocessor": {
"nonGlobalStepDefinitions": true,
Expand Down
24 changes: 8 additions & 16 deletions tests/cypress/support/authentication.js
@@ -1,7 +1,8 @@
// element selectors
const cognitoEmailInputField = "//input[@name='email']";
const cognitoPasswordInputField = "//input[@name='password']";
const cognitoEmailInputField = 'input[name="email"]';
const cognitoPasswordInputField = 'input[name="password"]';
const cognitoLoginButton = "[data-testid='cognito-login-button']";
const myAccountButton = '[aria-label="my account"';

const stateUserPassword = Cypress.env("STATE_USER_PASSWORD");
const adminUserPassword = Cypress.env("ADMIN_USER_PASSWORD");
Expand Down Expand Up @@ -41,6 +42,7 @@ Cypress.Commands.add("clearSession", () => {
Cypress.Commands.add("authenticate", (userType, userCredentials) => {
cy.session([userType, userCredentials], () => {
cy.visit("/");
cy.wait(2000);
let credentials = {};

if (userType && userCredentials) {
Expand All @@ -65,8 +67,8 @@ Cypress.Commands.add("authenticate", (userType, userCredentials) => {
throw new Error("Must specify either userType or userCredentials.");
}

cy.xpath(cognitoEmailInputField).type(credentials.email);
cy.xpath(cognitoPasswordInputField).type(credentials.password, {
cy.get(cognitoEmailInputField).type(credentials.email);
cy.get(cognitoPasswordInputField).type(credentials.password, {
log: false,
});
cy.get(cognitoLoginButton).click();
Expand All @@ -76,17 +78,7 @@ Cypress.Commands.add("authenticate", (userType, userCredentials) => {
* This ensures reused sessions maintain these tokens
* We expect at least three for the id, access, and refresh tokens
*/
cy.waitUntil(() =>
cy
.window()
.then(
(window) =>
Object.keys(window.localStorage).filter((key) =>
key.match(
/CognitoIdentityServiceProvider.+(refresh|access|id)Token/
)
).length === 3
)
);
Comment on lines -79 to -90
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the authentication call. It was using xpath which is deprecated, and I've removed with waitUtil and key check. Wait until wasn't in the docs from what I could find, and so I really tried to simplify the login process to be safe. That being said, calling login from the ui is an anti-pattern I guess? At least according to cypress. Some information about how we're actually supposed to call it is here but I simplified it to work with ours.

cy.wait(4500);
cy.get(myAccountButton).should("exist");
});
});
2 changes: 0 additions & 2 deletions tests/cypress/support/index.js
Expand Up @@ -3,9 +3,7 @@
* Read more here: https://on.cypress.io/configuration
*/

import "@cypress/xpath";
import "cypress-axe";
import "cypress-wait-until";
import "./accessibility";
import "./authentication";
import "@testing-library/cypress/add-commands";
32 changes: 21 additions & 11 deletions tests/cypress/support/step_definitions/formInputs.js
Expand Up @@ -18,23 +18,33 @@ When("these form elements are edited:/filled:", function (dataTable) {
const inputValue = row[2];
switch (inputType) {
case "singleCheckbox":
if (inputValue == "true") input.check().blur();
else input.uncheck();
if (inputValue == "true") {
input.check();
input.blur();
} else input.uncheck();
Comment on lines +21 to +24
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separating out chaining commands. You can read why from the cypress docs here: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Chains-of-Commands

break;
case "radio":
input.check(inputValue).blur();
input.check(inputValue);
input.blur();
break;
case "checkbox":
input.check(inputValue).blur();
input.check(inputValue);
input.blur();
break;
case "dropdown":
input.select(inputValue).blur();
input.select(inputValue);
input.blur();
break;
case "repeated":
input.eq(repeatedInput).clear().type(inputValue).blur();
input.eq(repeatedInput);
input.clear();
input.type(inputValue);
input.blur();
break;
default:
input.clear().type(inputValue).blur();
input.clear();
input.type(inputValue);
input.blur();
break;
}
if (this.delay) cy.wait(this.delay);
Expand All @@ -45,9 +55,8 @@ When("these form elements are prefilled and disabled:", (dataTable) => {
dataTable.rawTable.forEach((row) => {
switch (row[1]) {
default:
cy.get(`[name='${row[0]}']`)
.should("have.value", row[2])
.should("be.disabled");
cy.get(`[name='${row[0]}']`).should("have.value", row[2]);
cy.get(`[name='${row[0]}']`).should("be.disabled");
break;
}
});
Expand All @@ -69,7 +78,8 @@ When("these form elements are prefilled:", (dataTable) => {

When("the form is submitted", () => {
const submitButton = "[type='submit']";
cy.get(submitButton).focus().click();
cy.get(submitButton).focus();
cy.get(submitButton).click();
});

When("I click the {string} button", (name) => {
Expand Down
12 changes: 3 additions & 9 deletions tests/cypress/tests/e2e/footer.spec.js
Expand Up @@ -17,15 +17,9 @@ describe("Footer integration tests", () => {
});

it("Footer accessibility statement link navigates to the right external URL", () => {
cy.contains(accessibilityStatementLinkText)
.invoke("attr", "href")
.should(
"eq",
"https://www.cms.gov/About-CMS/Agency-Information/Aboutwebsite/CMSNondiscriminationNotice"
);
cy.contains(accessibilityStatementLinkText)
.invoke("attr", "target")
.should("eq", "_blank");
cy.get(
'a[href="https://www.cms.gov/About-CMS/Agency-Information/Aboutwebsite/CMSNondiscriminationNotice"]'
).contains(accessibilityStatementLinkText);
Comment on lines +20 to +22
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a slightly better way of calling and erroring if the footer link isn't found.


cy.contains(accessibilityStatementLinkText).then((link) => {
cy.request(link.prop("href")).its("status").should("eq", 200);
Expand Down