Skip to content

Commit

Permalink
Improve E2E tests (#723)
Browse files Browse the repository at this point in the history
* Fix Playwright config

* Add docs for E2E testing
  • Loading branch information
avine authored Nov 27, 2024
1 parent ea1bfc8 commit 87b60ba
Show file tree
Hide file tree
Showing 146 changed files with 485 additions and 184 deletions.
1 change: 1 addition & 0 deletions client/e2e/pages/feedback-history.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class FeedbackHistoryPage {

async goto(type: 'received' | 'given' | 'sentRequest' | 'receivedRequest') {
if (type === 'receivedRequest') {
// Dedicated page to list the received requests
await this.page.goto(`/fr/give-requested`);
} else {
await this.page.goto(`/fr/history/type/${type}`);
Expand Down
2 changes: 2 additions & 0 deletions client/e2e/pages/give-requested-feedback.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class GiveRequestedFeedbackPage {
constructor(private page: Page) {}

async give(persona: Persona, details: Details) {
await this.page.waitForURL('/fr/give-requested/token/**');

await expect(this.page.getByLabel('Email de votre collègue')).toHaveValue(persona);

await this.page.getByText('Points positifs').fill(details.positive);
Expand Down
60 changes: 60 additions & 0 deletions client/e2e/pages/manager.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Page, expect } from '@playwright/test';
import { Persona } from './sign-in.page';

type Details = {
message?: string; // note: there's no message for spontaneous feedback
positive: string;
negative: string;
comment: string;
};

export class ManagerPage {
constructor(private page: Page) {}

async goto() {
await this.page.goto('/fr/manager');
}

async selectManaged(persona: Persona) {
await this.page.getByLabel('Collaborateur').click();
await this.page.getByRole('option', { name: persona }).click();
}

async findGiverDetailsLink(persona: Persona) {
// Wait until the the `<table>` is rendered
await this.page.locator('tbody').waitFor();

return this.page
.locator('tbody > tr', { has: this.page.getByRole('cell', { name: persona }) })
.getByLabel('Consulter'); // note: the label can be 'Consulter la demande' or 'Consulter le feedZback'
}

async matchPendingFeedback(giver: Persona, receiver: Persona, message: string) {
await this.page.waitForURL('/fr/manager/document/**');

await expect(this.page.getByRole('heading', { name: 'Demande de feedZback partagé' })).toBeVisible();

await expect(this.page.getByText(giver)).toBeVisible();
await expect(this.page.getByText(receiver)).toBeVisible();

if (message) {
await expect(this.page.getByText(message)).toBeVisible();
}
}

async matchDoneFeedback(giver: Persona, receiver: Persona, details: Details) {
await this.page.waitForURL('/fr/manager/document/**');

await expect(this.page.getByRole('heading', { name: 'FeedZback partagé' })).toBeVisible();

await expect(this.page.getByText(giver)).toBeVisible();
await expect(this.page.getByText(receiver)).toBeVisible();

if (details.message) {
await expect(this.page.getByText(details.message)).toBeVisible();
}
await expect(this.page.getByText(details.positive)).toBeVisible();
await expect(this.page.getByText(details.negative)).toBeVisible();
await expect(this.page.getByText(details.comment)).toBeVisible();
}
}
20 changes: 20 additions & 0 deletions client/e2e/pages/settings.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Page } from '@playwright/test';
import { Persona } from './sign-in.page';

export class SettingsPage {
constructor(private page: Page) {}

async goto() {
await this.page.goto('/fr/settings');
}

async setManager(persona: Persona) {
await this.page.getByLabel('Email de votre manager').fill(persona);
await this.page.getByRole('button', { name: 'Mettre à jour' }).click();
}

async gotoAndSetManager(persona: Persona) {
await this.goto();
await this.setManager(persona);
}
}
4 changes: 3 additions & 1 deletion client/e2e/pages/user-menu.page.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Page } from '@playwright/test';
import { expect, Page } from '@playwright/test';

export class UserMenuPage {
constructor(private page: Page) {}

async logout() {
expect(this.page.url()).not.toMatch(/sign-in/);

await this.page.getByLabel('Menu utilisateur').click();
await this.page.getByRole('menuitem', { name: 'Se déconnecter' }).click();

Expand Down
33 changes: 33 additions & 0 deletions client/e2e/requested-feedback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { FeedbackHistoryDetailsPage } from './pages/feedback-history-details.pag
import { FeedbackHistoryPage } from './pages/feedback-history.page';
import { FirestorePage } from './pages/firestore.page';
import { GiveRequestedFeedbackPage } from './pages/give-requested-feedback.page';
import { ManagerPage } from './pages/manager.page';
import { RequestFeedbackPage } from './pages/request-feedback.page';
import { SettingsPage } from './pages/settings.page';
import { Persona, SignInPage } from './pages/sign-in.page';
import { UserMenuPage } from './pages/user-menu.page';

Expand Down Expand Up @@ -81,4 +83,35 @@ test('Requested feedback', async ({ page }) => {
await bernardDetailsLink2.click();

await new FeedbackHistoryDetailsPage(page).matchDone('received', Persona.Bernard, feedbackDetails);

// ====== Alfred sets Daniel as its manager ======

await new SettingsPage(page).gotoAndSetManager(Persona.Daniel);
await new UserMenuPage(page).logout();

// ====== Daniel can now view the feedbacks that Alfred has shared with him ======

await new SignInPage(page).gotoAndSignIn(Persona.Daniel);

await expect(page.getByRole('link', { name: 'Manager' })).toBeVisible();

const managerPage = new ManagerPage(page);
managerPage.goto();
managerPage.selectManaged(Persona.Alfred);

// View the feedback sent by Bernard
const bernardDetailsLink3 = await managerPage.findGiverDetailsLink(Persona.Bernard);
await expect(bernardDetailsLink3).toBeVisible();
await bernardDetailsLink3.click();
await managerPage.matchDoneFeedback(Persona.Bernard, Persona.Alfred, feedbackDetails);

// Go back to the list of shared feedbacks
await page.getByRole('button', { name: 'FeedZbacks partagés' }).click();
await page.waitForURL('/fr/manager/list/**');

// View the feedback expected from Charles
const charlesDetailsLink2 = await managerPage.findGiverDetailsLink(Persona.Charles);
await expect(charlesDetailsLink2).toBeVisible();
await charlesDetailsLink2.click();
await managerPage.matchPendingFeedback(Persona.Charles, Persona.Alfred, feedbackDetails.message);
});
23 changes: 23 additions & 0 deletions client/e2e/spontaneous-feedback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { FeedbackHistoryDetailsPage } from './pages/feedback-history-details.pag
import { FeedbackHistoryPage } from './pages/feedback-history.page';
import { FirestorePage } from './pages/firestore.page';
import { GiveSpontaneousFeedbackPage } from './pages/give-spontaneous-feedback.page';
import { ManagerPage } from './pages/manager.page';
import { SettingsPage } from './pages/settings.page';
import { Persona, SignInPage } from './pages/sign-in.page';
import { UserMenuPage } from './pages/user-menu.page';

Expand Down Expand Up @@ -41,4 +43,25 @@ test('Spontaneous feedback', async ({ page }) => {
await bernardDetailsLink.click();

await new FeedbackHistoryDetailsPage(page).matchDone('received', Persona.Bernard, feedbackDetails);

// ====== Alfred sets Daniel as its manager ======

await new SettingsPage(page).gotoAndSetManager(Persona.Daniel);
await new UserMenuPage(page).logout();

// ====== Daniel can now view the feedback that Alfred has shared with him ======

await new SignInPage(page).gotoAndSignIn(Persona.Daniel);

await expect(page.getByRole('link', { name: 'Manager' })).toBeVisible();

const managerPage = new ManagerPage(page);
managerPage.goto();
managerPage.selectManaged(Persona.Alfred);

const managerDetailsLink = await managerPage.findGiverDetailsLink(Persona.Bernard);
await expect(managerDetailsLink).toBeVisible();
await managerDetailsLink.click();

await managerPage.matchDoneFeedback(Persona.Bernard, Persona.Alfred, feedbackDetails);
});
8 changes: 4 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
"server:e2e": "npm --prefix ../server run start:e2e",
"prestack:e2e": "npm run build:e2e",
"stack:e2e": "concurrently \"npm run firebase:e2e\" \"npm run server:e2e\"",
"test:e2e": "playwright test",
"posttest:e2e": "playwright show-report",
"test-ui:e2e": "playwright test --ui",
"test:codegen": "playwright codegen"
"e2e:test": "playwright test",
"e2e:report": "playwright show-report",
"e2e:ui": "playwright test --ui",
"e2e:codegen": "playwright codegen"
},
"private": true,
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions client/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ export default defineConfig({

/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run e2e:app',
command: 'npm run stack:e2e',
reuseExistingServer: !process.env['CI'],
port: 4200,
timeout: 30 * 1000,
timeout: 60_000,
stdout: 'pipe',
},
});
22 changes: 17 additions & 5 deletions docs-source/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ To configure and run the application locally, you need access to the Firebase pr

Once you have gained access, you will also be able to access the [Google Cloud console](https://console.cloud.google.com/?project=feedzback-v2-dev).

:::note
:::info
If you don't have permission, please contact Pierre Nicoli or Stéphane Francel.
:::

## Server

- Open your IDE in `./server` directory
- Open your IDE in `<rootDir>/server` directory

### Configuration

Expand Down Expand Up @@ -44,20 +44,32 @@ Retrieve these secret values from the [Google Cloud Run console](https://console

```bash
npm install
npm run start:dev
npm run start:dev # Start the server in "watch" mode
```

Visit the URL http://localhost:3000 to check that the server is running properly.

:::tip
If you just need to start the server once (without "watch" mode), run the command `npm start` from the `<rootDir>/server` directory.

Running the command `npm run server` from the `<rootDir>/client` directory will have the same effect.
:::

## Client

### Installation

- Open your IDE in `./client` directory
- Open your IDE in `<rootDir>/client` directory

- Run the following commands:

```bash
npm install
npm start
npm start # Start the client in "watch" mode
```

Finally, visit the URL http://localhost:4200 and enjoy FeedZback! 🚀

:::info
Please refer to [Local dev environment](/docs/technical-guides/environments/local-dev) for other ways to launch the application.
:::
2 changes: 1 addition & 1 deletion docs-source/docs/technical-guides/client/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Note the post-processing script `postextract-i18n`, which sorts the translations

The target file [src/locales/messages.en.json](https://github.com/Zenika/feedzback/blob/main/client/src/locales/messages.en.json) was created and translated manually.

:::note
:::tip
Don't forget to regenerate the source file every time you add or modify text in the source code.
Then update the destination file accordingly.
:::
Expand Down
2 changes: 1 addition & 1 deletion docs-source/docs/technical-guides/client/styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ And the Sass styles should looks like this:
}
```

:::note
:::info
The [`src/app/app.scss`](https://github.com/Zenika/feedzback/blob/main/client/src/app/app.scss) file gives you access to the global Sass variables and mixins in the component styles.
:::

Expand Down
83 changes: 83 additions & 0 deletions docs-source/docs/technical-guides/e2e-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# E2E testing

E2E tests are performed using [Playwright](https://playwright.dev/).

## NPM scripts

To run the tests, open a terminal in `<rootDir>/client` directory and run the following command:

```bash
npm run e2e:test
```

All scripts related to E2E tests start with `"e2e:*"`:

```json title="<rootDir>/client/package.json"
{
"scripts": {
"e2e:test": "playwright test",
"e2e:report": "playwright show-report",
"e2e:ui": "playwright test --ui",
"e2e:codegen": "playwright codegen"
}
}
```

## Playwright configuration

Here's part of the Playwright configuration:

```ts title="<rootDir>/client/playwright.config.ts"
import { defineConfig } from '@playwright/test';

export default defineConfig({
// Tests are located in the following directory
testDir: './e2e',

// Run your local dev server before starting the tests
webServer: {
command: 'npm run stack:e2e',
port: 4200,
reuseExistingServer: !process.env['CI'],
},
});
```

Before starting the tests, Playwright executes the command `npm run stack:e2e` and waits for the application to be available on port `4200`.
Due to the `reuseExistingServer` option (enabled for non-CI environment), the command will not be executed if the application is already available.

Therefore, you can run the command `npm run stack:e2e` in one terminal, wait for the application to be available on port `4200`, and then run the command `npm run e2e:test` in another terminal.
In this case, Playwright will skip the `webServer.command`, starting the tests immediately.

## Running Playwright

### Method 1

To have Playwright start the app and the tests, run the following command:

```bash
npm run e2e:test
```

:::warning
At the end of the tests, Playwright may not stop the `webServer` properly.
If this happens, look for a "ghost" process named `java` and kill it manually.

To avoid this problem, use the method 2 instead.
:::

### Method 2

To start the app once and then have Playwright only start the tests, run the following commands in two separate terminals:

```bash
npm run stack:e2e # First terminal
```

```bash
npm run e2e:test # Second terminal
```

:::tip
As an alternative to the above command in the first terminal, run `npm run stack:emulators` instead, to start the app in "watch" mode.
:::
Loading

0 comments on commit 87b60ba

Please sign in to comment.