Skip to content

Commit

Permalink
Merge branch 'main' of github.com:cfpb/sbl-frontend into 943-disable-…
Browse files Browse the repository at this point in the history
…ufp-rssd-id
  • Loading branch information
shindigira committed Oct 15, 2024
2 parents 5d82126 + eb225ac commit 4e56a04
Show file tree
Hide file tree
Showing 54 changed files with 1,057 additions and 193 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ SBL_LOGOUT_REDIRECT_URL=""
SBL_VALIDATION_TIMEOUT_SECONDS="1200"
SBL_LONGPOLLING_DELAY_SECONDS="backoff"
SBL_UPLOAD_FILE_SIZE_LIMIT_BYTES="50000000"
SBL_ENABLE_PLAYWRIGHT_TEST_SETTINGS="false"
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @billhimmelsbach @meissadia @shindigira @ojbravo
* @billhimmelsbach @meissadia @shindigira @ojbravo @tanner-ricks
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ env:
SBL_VALIDATION_TIMEOUT_SECONDS: "1200"
SBL_LONGPOLLING_DELAY_SECONDS: "backoff"
SBL_UPLOAD_FILE_SIZE_LIMIT_BYTES: "50000000"

SBL_ENABLE_PLAYWRIGHT_TEST_SETTINGS: "false"

jobs:
React:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ coverage
.stylelintcache
cypress/videos
package-lock.json
e2e/pages/filing-app/uploadFile/downloads/*
e2e/utils/downloads/*

.yarn/*
!.yarn/cache
Expand Down
1 change: 1 addition & 0 deletions ENV-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ SBL_LOGOUT_REDIRECT_URL=""
SBL_VALIDATION_TIMEOUT_SECONDS="1200"
SBL_LONGPOLLING_DELAY_SECONDS="backoff"
SBL_UPLOAD_FILE_SIZE_LIMIT_BYTES="50000000"
SBL_ENABLE_PLAYWRIGHT_TEST_SETTINGS="false"
```

### To add a new environment variable
Expand Down
40 changes: 14 additions & 26 deletions e2e/example.spec.demo.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// this test won't run, but may be helpful as an example when writing future tests

import { expect } from '@playwright/test';
import path from 'node:path';
import { test } from './fixtures/testFixture';
import pointOfContactJson from './test-data/point-of-contact/point-of-contact-data-1.json';
import { ResultUploadMessage, uploadFile } from './utils/uploadFile';

test('proof of concept', async ({ page }) => {
test.slow();
const tenSecondTimeout = 10_000;
const sixtySecondTimeout = 60_000;
const minorDelay = 500;

await test.step('Unauthenticated homepage: navigate to Authenticated homepage', async () => {
Expand Down Expand Up @@ -45,28 +43,15 @@ test('proof of concept', async ({ page }) => {
});

await test.step('Upload file: navigate to Review warnings after only warnings upload', async () => {
await test.step('Upload file: upload small file with only warnings (sbl-validations-all-pass-small.csv)', async () => {
await expect(page.locator('h2')).toContainText('Select a file to upload');
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByLabel('Select a .csv file to upload').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(
// eslint-disable-next-line unicorn/prefer-module
__dirname,
'./test-data/sample-sblar-files/sbl-validations-all-pass-small.csv',
),
);
await expect(page.getByText('File upload in progress')).toBeVisible();
await expect(page.getByText('File upload successful')).toBeVisible({
timeout: tenSecondTimeout,
});
await expect(page.getByText('Validation checks in progress')).toBeVisible(
{ timeout: tenSecondTimeout },
);
await expect(
page.getByText('Warnings were found in your file'),
).toBeVisible({ timeout: sixtySecondTimeout });
await uploadFile({
testUsed: test,
pageUsed: page,
newUpload: true,
testTitle:
'Upload file: upload small file with only warnings (sbl-validations-all-pass-small.csv)',
filePath:
'../test-data/sample-sblar-files/sbl-validations-all-pass-small.csv',
resultMessage: ResultUploadMessage.warning,
});

await test.step('Upload file: navigate to Resolve errors (syntax) with no errors after upload', async () => {
Expand Down Expand Up @@ -98,8 +83,11 @@ test('proof of concept', async ({ page }) => {
await page.getByLabel('First name').fill(pointOfContactJson.first_name);
await page.getByLabel('Last name').fill(pointOfContactJson.last_name);
await page
.getByLabel('Work phone numberPhone number')
.getByLabel('Phone numberPhone number')
.fill(pointOfContactJson.phone_number);
await page
.getByLabel('Extension (optional)Extension')
.fill(pointOfContactJson.phone_ext);
await page
.getByLabel('Email addressEmail address')
.fill(pointOfContactJson.email);
Expand Down
114 changes: 89 additions & 25 deletions e2e/fixtures/testFixture.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Page } from '@playwright/test';
import { test as baseTest, expect } from '@playwright/test';
import path from 'node:path';
import pointOfContactJson from '../test-data/point-of-contact/point-of-contact-data-1.json';
import createDomainAssociation from '../utils/createDomainAssociation';
import createInstitution from '../utils/createInstitution';
Expand All @@ -12,6 +11,7 @@ import {
expectedWithAssociationsUrl,
getTestDataObject,
} from '../utils/testFixture.utils';
import { ResultUploadMessage, uploadFile } from '../utils/uploadFile';

export const test = baseTest.extend<{
isNonAssociatedUser: boolean; // Skips creating a domain association and creating a financial institution
Expand All @@ -22,6 +22,8 @@ export const test = baseTest.extend<{
navigateToProvideTypeOfFinancialInstitution: Page;
navigateToUploadFile: Page;
navigateToReviewWarningsAfterOnlyWarningsUpload: Page;
navigateToSyntaxErrorsAfterSyntaxErrorsUpload: Page;
navigateToLogicErrorsAfterLogicErrorsUpload: Page;
navigateToProvidePointOfContact: Page;
navigateToSignAndSubmit: Page;
}>({
Expand Down Expand Up @@ -98,6 +100,7 @@ export const test = baseTest.extend<{
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.locator('h1')).toContainText(
'Complete your user profile',
{ timeout: 30_000 },
);

// Two versions of Complete User Profile - with and without associations
Expand Down Expand Up @@ -189,35 +192,92 @@ export const test = baseTest.extend<{
});
},

navigateToReviewWarningsAfterOnlyWarningsUpload: async (
navigateToSyntaxErrorsAfterSyntaxErrorsUpload: async (
{ page, navigateToUploadFile },
use,
) => {
navigateToUploadFile;
await test.step('Upload file: navigate to Review warnings after only warnings upload', async () => {
await test.step('Upload file: upload small file with only warnings (sbl-validations-all-pass-small.csv)', async () => {
await expect(page.locator('h2')).toContainText(
'Select a file to upload',
await test.step('Upload file: navigate to Syntax Errors page after only errors upload', async () => {
await uploadFile({
testUsed: test,
pageUsed: page,
newUpload: true,
testTitle:
'Upload file: upload small file with only syntax errors (errors-page-1-syntax-few.csv)',
filePath:
'../test-data/sample-sblar-files/errors-page-1-syntax-few.csv',
resultMessage: ResultUploadMessage.syntax,
});

await test.step('Upload file: navigate to Resolve errors (syntax) with no errors after upload', async () => {
await page.waitForSelector('#nav-next');
await page.waitForTimeout(500);
await page
.getByRole('button', { name: 'Continue to next step' })
.click();
await expect(page.locator('h1')).toContainText(
'Resolve errors (syntax)',
);
});

await use(page);
});
},

navigateToLogicErrorsAfterLogicErrorsUpload: async (
{ page, navigateToUploadFile },
use,
) => {
navigateToUploadFile;
await test.step('Upload file: navigate to Logic Errors page after only errors upload', async () => {
await uploadFile({
testUsed: test,
pageUsed: page,
newUpload: true,
testTitle:
'Upload file: upload small file with only logic errors (errors-page-2-logic-few.csv)',
filePath:
'../test-data/sample-sblar-files/logic-errors_single&multi_and_warnings.csv',
resultMessage: ResultUploadMessage.logic,
});

await test.step('Upload file: navigate to Resolve errors (syntax) with no errors after upload', async () => {
await page.waitForSelector('#nav-next');
await page.waitForTimeout(500);
await page
.getByRole('button', { name: 'Continue to next step' })
.click();
await expect(page.locator('h1')).toContainText(
'Resolve errors (syntax)',
);
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByLabel('Select a .csv file to upload').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(
__dirname,
'../test-data/sample-sblar-files/sbl-validations-all-pass-small.csv',
),
});

await test.step('Resolve errors (logic): navigate to Resolve errors (logic) with errors after upload', async () => {
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.locator('h1')).toContainText(
'Resolve errors (logic)',
);
await expect(page.getByText('File upload in progress')).toBeVisible();
await expect(page.getByText('File upload successful')).toBeVisible({
timeout: 10_000,
});
await expect(
page.getByText('Validation checks in progress'),
).toBeVisible({ timeout: 10_000 });
await expect(
page.getByText('Warnings were found in your file'),
).toBeVisible({ timeout: 60_000 });
});

await use(page);
});
},

navigateToReviewWarningsAfterOnlyWarningsUpload: async (
{ page, navigateToUploadFile },
use,
) => {
navigateToUploadFile;
await test.step('Upload file: navigate to Review warnings after only warnings upload', async () => {
await uploadFile({
testUsed: test,
pageUsed: page,
newUpload: true,
testTitle:
'Upload file: upload small file with only warnings (sbl-validations-all-pass-small.csv)',
filePath:
'../test-data/sample-sblar-files/sbl-validations-all-pass-small.csv',
resultMessage: ResultUploadMessage.warning,
});

await test.step('Upload file: navigate to Resolve errors (syntax) with no errors after upload', async () => {
Expand Down Expand Up @@ -258,6 +318,7 @@ export const test = baseTest.extend<{
await page.getByRole('button', { name: 'Continue to next step' }).click();
await expect(page.locator('h1')).toContainText(
'Provide point of contact',
{ timeout: 30_000 },
);
});
await use(page);
Expand All @@ -273,8 +334,11 @@ export const test = baseTest.extend<{
await page.getByLabel('First name').fill(pointOfContactJson.first_name);
await page.getByLabel('Last name').fill(pointOfContactJson.last_name);
await page
.getByLabel('Work phone numberPhone number')
.getByLabel('Phone numberPhone number')
.fill(pointOfContactJson.phone_number);
await page
.getByLabel('Extension (optional)Extension')
.fill(pointOfContactJson.phone_ext);
await page
.getByLabel('Email addressEmail address')
.fill(pointOfContactJson.email);
Expand Down
39 changes: 39 additions & 0 deletions e2e/pages/filing-app/filing-step-routing/_shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable no-await-in-loop */

import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';
import type { test } from '../../../fixtures/testFixture';

interface VerifyRedirects {
page: Page;
userShouldNotAccess: string[]; // List of paths that a user should not be able to access
afterRedirectURL: RegExp; // RegExp that matches the URL a user should land on after redirect
afterRedirectHeading: string; // H1 text a user should see after redirect
currentStepPath: string; // A string at which to split the URL while preserving the LEI
testLabel: string; // Label displayed in the Playwright logs
test: typeof test;
}

/**
* Ensure that, from the current Filing step, users cannot inappropriately access future steps.
*/
export const verifyRedirects = async ({
page,
test,
userShouldNotAccess,
afterRedirectURL,
afterRedirectHeading,
currentStepPath,
testLabel,
}: VerifyRedirects) => {
const [baseURL] = page.url().split(currentStepPath);

// Note: Tests failed when using Promise.all in place of this loop approach
for (const futureStep of userShouldNotAccess) {
await test.step(`${testLabel}: Verify user cannot access ${futureStep}`, async () => {
await page.goto(baseURL + futureStep);
await expect(page).toHaveURL(afterRedirectURL, { timeout: 10_000 });
await expect(page.locator('h1')).toContainText(afterRedirectHeading);
});
}
};
30 changes: 30 additions & 0 deletions e2e/pages/filing-app/filing-step-routing/errorsLogic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { test } from '../../../fixtures/testFixture';
import { verifyRedirects } from './_shared';

const testLabel = 'Filing step routing (Errors: Logic)';

const currentStepPath = '/error';

const userShouldNotAccess = ['/warnings', '/contact', '/submit'];

const afterRedirectHeading = 'Resolve errors (syntax)';
const afterRedirectURL = /.*errors\/errors-syntax$/;

test(
testLabel,
async ({ page, navigateToLogicErrorsAfterLogicErrorsUpload }) => {
test.slow();

navigateToLogicErrorsAfterLogicErrorsUpload;

await verifyRedirects({
afterRedirectHeading,
afterRedirectURL,
currentStepPath,
page,
test,
testLabel,
userShouldNotAccess,
});
},
);
35 changes: 35 additions & 0 deletions e2e/pages/filing-app/filing-step-routing/errorsSyntax.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test } from '../../../fixtures/testFixture';
import { verifyRedirects } from './_shared';

const testLabel = 'Filing step routing (Errors: Syntax)';

const currentStepPath = '/error';

const userShouldNotAccess = [
'/errors/errors-logic',
'/warnings',
'/contact',
'/submit',
];

const afterRedirectHeading = 'Resolve errors (syntax)';
const afterRedirectURL = /.*errors\/errors-syntax$/;

test(
testLabel,
async ({ page, navigateToSyntaxErrorsAfterSyntaxErrorsUpload }) => {
test.slow();

navigateToSyntaxErrorsAfterSyntaxErrorsUpload;

await verifyRedirects({
afterRedirectHeading,
afterRedirectURL,
currentStepPath,
page,
test,
testLabel,
userShouldNotAccess,
});
},
);
Loading

0 comments on commit 4e56a04

Please sign in to comment.