Skip to content

Commit

Permalink
Fix: 726 changepassword cypress fix + firebase emulator (#751)
Browse files Browse the repository at this point in the history
* firebase emulator added

* changepassword e2e tests fixed
  • Loading branch information
Alessandro100 authored Oct 16, 2024
1 parent b2a7289 commit ad41129
Show file tree
Hide file tree
Showing 16 changed files with 176 additions and 114 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/web-app-deployer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ jobs:
- name: Cypress test
uses: cypress-io/github-action@v6
with:
start: yarn start:test
wait-on: "npx wait-on --timeout 120000 http://127.0.0.1:3000"
start: |
yarn start:test
npx firebase emulators:start --only auth --project mobility-feeds-dev
wait-on: npx wait-on --timeout 120000 http://127.0.0.1:3000 http://127.0.0.1:9099
working-directory: web-app

- uses: actions/upload-artifact@v4
Expand Down
4 changes: 4 additions & 0 deletions web-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ npx firebase hosting:channel:deploy {channel_name}
Component and E2E tests are executed with [Cypress](https://docs.cypress.io/). Cypress tests are located in the cypress folder.

Cypress useful commands:
- Run the firebase emulator in a separate terminal
```
yarn run firebase:auth:emulator:dev
```
- Run local headless tests
```
yarn start:dev
Expand Down
10 changes: 10 additions & 0 deletions web-app/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { defineConfig } from 'cypress';
import * as dotenv from 'dotenv';
const localEnv = dotenv.config({ path: './src/.env.dev' }).parsed;
const ciEnv = dotenv.config({ path: './src/.env.test' }).parsed;

const isEnvEmpty = (obj) => {
return Object.keys(obj).length === 0;
};

const chosenEnv = isEnvEmpty(localEnv) ? ciEnv : localEnv;

export default defineConfig({
env: chosenEnv,
e2e: {
baseUrl: 'http://localhost:3000',
},
Expand Down
25 changes: 14 additions & 11 deletions web-app/cypress/e2e/addFeedForm.cy.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
describe('Add Feed Form', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');
cy.visit('/contribute');
cy.injectAuthenticatedUser();
cy.intercept('POST', '/writeToSheet', {
statusCode: 200,
body: {
result: { message: 'Data written to the new sheet successfully!' },
},
}).as('writeToSheet');
});
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');
cy.createNewUserAndSignIn('cypressTestUser@mobilitydata.org', 'BigCoolPassword123!');

cy.get('[data-cy="accountHeader"]').should('exist'); // assures that the user is signed in
cy.visit('/contribute');
// Assures that the firebase remote config has loaded for the first test
// Optimizations can be made to make the first test run faster
// Long timeout is to assure no flakiness
cy.get('[data-cy=isOfficialProducerYes]', { timeout: 25000 }).should('exist');
});

describe('Success Flows', () => {
it('should submit a new gtfs scheduled feed as official producer', () => {
cy.get('[data-cy=isOfficialProducerYes]', { timeout: 6000 }).click({
cy.get('[data-cy=isOfficialProducerYes]').click({
force: true,
});
cy.get('[data-cy=feedLink] input').type('https://example.com/feed', {
Expand Down Expand Up @@ -49,7 +54,7 @@ describe('Add Feed Form', () => {
cy.url().should('include', '/contribute?step=2');
// step 2
cy.get('[data-cy=serviceAlertFeed] input').type(
'https://example.com/feed/realtime'
'https://example.com/feed/realtime',
);
cy.get('[data-cy=secondStepRtSubmit]').click();
cy.url().should('include', '/contribute?step=3');
Expand All @@ -74,9 +79,7 @@ describe('Add Feed Form', () => {
cy.get('[data-cy=feedLink] input').type('https://example.com/feed', {
force: true,
});
cy.get('[data-cy=oldFeedLink] input').type(
'https://example.com/feedOld'
);
cy.get('[data-cy=oldFeedLink] input').type('https://example.com/feedOld');
cy.get('[data-cy=submitFirstStep]').click();
// Step 2
cy.get('[data-cy=secondStepSubmit]').click();
Expand Down
96 changes: 29 additions & 67 deletions web-app/cypress/e2e/changepassword.cy.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,61 @@
const email = Cypress.env('email');
const currentPassword = Cypress.env('currentPassword');
const newPassword = Cypress.env('currentPassword') + 'TEST';

let beforeEachFailed = false;

describe.skip('Change Password Screen', () => {
before(() => {});
const currentPassword = 'IloveOrangeCones123!';
const newPassword = currentPassword + 'TEST';
const email = 'cypressTestUser@mobilitydata.org';

describe('Change Password Screen', () => {
beforeEach(() => {
beforeEachFailed = false;
// As per issue #458 the beforeEach of this test file sometimes fail.
// Instead of failing the tests in that case issue a warning.
// This should be removed once the issue is resolved.
try {
// Visit the login page and login
cy.visit('/sign-in');
cy.get('input[id="email"]').clear().type(email);
cy.get('input[id="password"]').clear().type(currentPassword);
cy.get('button[type="submit"]').click();
// Wait for the user to be redirected to the home page
cy.location('pathname').should('eq', '/account', { timeout: 30000 });
// Visit the change password page
cy.visit('/change-password');
} catch (error) {
beforeEachFailed = true;
cy.log(`Warning: ${error.message}`);
}
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');
cy.createNewUserAndSignIn(email, currentPassword);
cy.get('[data-cy="accountHeader"]').should('exist'); // assures that the user is signed in
cy.visit('/change-password');
});

it('should render components', () => {
if (beforeEachFailed) {
cy.log('Skipping test due to beforeEach failure');
return;
}
// Check that the current password field exists
cy.get('input[id="currentPassword"]').should('exist');

// Check that the new password field exists
cy.get('input[id="newPassword"]').should('exist');

// Check that the confirm new password field exists
cy.get('input[id="confirmNewPassword"]').should('exist');
});

it('should show error when current password is incorrect', () => {
if (beforeEachFailed) {
cy.log('Skipping test due to beforeEach failure');
return;
}
// Type the wrong current password
cy.get('input[id="currentPassword"]').type('wrong');

// Type the new password
cy.get('input[id="newPassword"]').type(newPassword);

// Confirm the new password
cy.get('input[id="confirmNewPassword"]').type(newPassword);

// Submit the form
cy.get('button[type="submit"]').click();

// Check that the error message is displayed
cy.contains(
'The password is invalid or the user does not have a password. (auth/wrong-password).',
).should('exist');
});

it('should change password', () => {
if (beforeEachFailed) {
cy.log('Skipping test due to beforeEach failure');
return;
}
// Type the current password
cy.intercept('POST', '/retrieveUserInformation', {
statusCode: 200,
body: {
result: {
uid: 'ep4EwJvgNhfEER152EfzLSI0MBG2',
isRegisteredToReceiveAPIAnnouncements: false,
organization: '',
fullName: 'Alessandro',
registrationCompletionTime: '2024-09-24T15:34:55.381Z',
},
},
});
cy.get('input[id="currentPassword"]').type(currentPassword);

// Type the new password
cy.get('input[id="newPassword"]').type(newPassword);

// Confirm the new password
cy.get('input[id="confirmNewPassword"]').type(newPassword);

// Submit the form
cy.get('button[type="submit"]').click();

// Check that the password was changed successfully
cy.contains('Change Password Succeeded').should('exist');
cy.get('[cy-data="goToAccount"]').click();
cy.location('pathname').should('eq', '/account');

// Reset the password back to the original password
cy.visit('/change-password');
cy.get('input[id="currentPassword"]').type(newPassword);
cy.get('input[id="newPassword"]').type(currentPassword);
cy.get('input[id="confirmNewPassword"]').type(currentPassword);
cy.get('button[type="submit"]').click();
cy.contains('Change Password Succeeded').should('exist');
// logout
cy.get('[data-cy="signOutButton"]').click();
cy.get('[data-cy="confirmSignOutButton"]').should('exist').click();
cy.visit('/sign-in');
cy.get('[data-cy="signInEmailInput"]').type(email);
cy.get('[data-cy="signInPasswordInput"]').type(newPassword);
cy.get('[data-testid="signin"]').click();
cy.location('pathname').should('eq', '/account');
});
});
37 changes: 35 additions & 2 deletions web-app/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,31 @@
// }
// }

Cypress.Commands.add('injectAuthenticatedUser', () => {
import firebase from 'firebase/compat/app';
import 'firebase/compat/remote-config';
import 'firebase/compat/auth';

const firebaseConfig = {
apiKey: Cypress.env('REACT_APP_FIREBASE_API_KEY'),
authDomain: Cypress.env('REACT_APP_FIREBASE_AUTH_DOMAIN'),
projectId: Cypress.env('REACT_APP_FIREBASE_PROJECT_ID'),
storageBucket: Cypress.env('REACT_APP_FIREBASE_STORAGE_BUCKET'),
messagingSenderId: Cypress.env('REACT_APP_FIREBASE_MESSAGING_SENDER_ID'),
appId: Cypress.env('REACT_APP_FIREBASE_APP_ID'),
};

const app = firebase.initializeApp(firebaseConfig);

app.auth().useEmulator('http://localhost:9099/');

Cypress.Commands.add('injectAuthenticatedUser', (email: string) => {
cy.window()
.its('store')
.invoke('dispatch', {
type: 'userProfile/loginSuccess',
payload: {
fullName: 'Valery',
email: 'testuser@gmail.com',
email: email,
isRegistered: true,
isEmailVerified: true,
organization: '',
Expand All @@ -68,3 +85,19 @@ Cypress.Commands.add(
Cypress.Commands.add('assetMuiError', (elementKey: string) => {
cy.get(elementKey).should('have.class', 'Mui-error');
});

Cypress.Commands.add(
'createNewUserAndSignIn',
(email: string, password: string) => {
const auth = app.auth();
cy.then(async () => {
await fetch(
'http://localhost:9099/emulator/v1/projects/mobility-feeds-dev/accounts',
{ method: 'DELETE' },
);
await auth.createUserWithEmailAndPassword(email, password);
await auth.signInWithEmailAndPassword(email, password);
cy.injectAuthenticatedUser(email);
});
},
);
48 changes: 28 additions & 20 deletions web-app/cypress/support/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import './commands';

declare global {
namespace Cypress {
interface Chainable {
/**
* Dispatches loginSuccess action to the store with the given user profile
* Simulates the login of a user
*/
injectAuthenticatedUser(): void;
namespace Cypress {
interface Chainable {
/**
* Dispatches loginSuccess action to the store with the given user profile
* Simulates the login of a user
* @param email email of the user to inject
*/
injectAuthenticatedUser(email: string): void;

/**
* Selects a dropdown item in a MUI dropdown
* @param elementKey selector of the dropdown element
* @param dropDownDataValue data value of the dropdown item to select
*/
muiDropdownSelect(elementKey: string, dropDownDataValue: string): void;
/**
* Selects a dropdown item in a MUI dropdown
* @param elementKey selector of the dropdown element
* @param dropDownDataValue data value of the dropdown item to select
*/
muiDropdownSelect(elementKey: string, dropDownDataValue: string): void;

/**
* Tests if an element has the MUI error
* @param elementKey selector of the element to assert the MUI error class
*/
assetMuiError(elementKey: string): void;
}
/**
* Tests if an element has the MUI error
* @param elementKey selector of the element to assert the MUI error class
*/
assetMuiError(elementKey: string): void;

/**
* Wipes the firebase auth state, creates a user and signs in. Injects the user into the store
* @param email email of the new user
* @param password password of the new user
*/
createNewUserAndSignIn(email: string, password: string): void;
}
}
}
}
3 changes: 3 additions & 0 deletions web-app/firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"storage": {
"port": 9199
},
"auth": {
"port": 9099
},
"ui": {
"enabled": true
},
Expand Down
4 changes: 3 additions & 1 deletion web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"lint:fix": "eslint 'src/app/**/*.{js,ts,tsx}' --fix",
"cypress:run": "cypress run",
"cypress:open": "cypress open",
"firebase:auth:emulator:dev": "firebase emulators:start --only auth --project mobility-feeds-dev",
"generate:api-types:output": "npx openapi-typescript ../docs/DatabaseCatalogAPI.yaml -o $OUTPUT_PATH_TYPES && eslint $OUTPUT_PATH_TYPES --fix",
"generate:api-types": "OUTPUT_PATH_TYPES=src/app/services/feeds/types.ts npm run generate:api-types:output"
},
Expand Down Expand Up @@ -103,7 +104,7 @@
"@types/jest": "^29.5.12",
"@types/material-ui": "^0.21.12",
"@types/mui-datatables": "^4.3.12",
"@types/node": "^20.8.10",
"@types/node": "^22.7.4",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.7",
"@types/react-google-recaptcha": "^2.1.8",
Expand All @@ -114,6 +115,7 @@
"@typescript-eslint/parser": "^6.7.0",
"babel-jest": "^29.7.0",
"cypress": "^13.2.0",
"dotenv": "^16.4.5",
"env-cmd": "^10.1.0",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
Expand Down
7 changes: 6 additions & 1 deletion web-app/src/app/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ const DrawerContent: React.FC<{
defaultExpandIcon={<ChevronRightIcon />}
sx={{ textAlign: 'left' }}
>
<TreeItem nodeId='1' label='Account' sx={{ color: '#3959fa' }}>
<TreeItem
nodeId='1'
label='Account'
sx={{ color: '#3959fa' }}
data-cy='accountHeader'
>
<TreeItem
nodeId='2'
label='Account Details'
Expand Down
7 changes: 6 additions & 1 deletion web-app/src/app/components/LogoutConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ export default function ConfirmModal({
>
Cancel
</Button>
<Button onClick={confirmLogout} color='primary' variant='contained'>
<Button
onClick={confirmLogout}
color='primary'
variant='contained'
data-cy='confirmSignOutButton'
>
Confirm
</Button>
</DialogActions>
Expand Down
Loading

0 comments on commit ad41129

Please sign in to comment.