Skip to content

Commit 73d7a45

Browse files
authored
feat: e2e tests (#121)
1 parent b457e3f commit 73d7a45

20 files changed

+656
-5
lines changed

.github/workflows/e2e-test.yml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: E2E Tests
2+
3+
on:
4+
pull_request:
5+
branches: [dev, main]
6+
7+
jobs:
8+
e2e-test:
9+
runs-on: ubuntu-latest
10+
11+
services:
12+
mysql:
13+
image: mysql:8.0.19
14+
env:
15+
MYSQL_ROOT_PASSWORD: userfeedback
16+
MYSQL_DATABASE: e2e
17+
MYSQL_USER: userfeedback
18+
MYSQL_PASSWORD: userfeedback
19+
TZ: UTC
20+
ports:
21+
- 13307:3306
22+
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
23+
24+
smtp:
25+
image: rnwood/smtp4dev:v3
26+
ports:
27+
- 5080:80
28+
- 25:25
29+
- 143:143
30+
31+
# opensearch:
32+
# image: opensearchproject/opensearch:2.4.1
33+
# ports:
34+
# - 9200:9200
35+
36+
steps:
37+
- name: Check out repository code
38+
uses: actions/checkout@main
39+
40+
- name: Build and run
41+
run: |
42+
docker-compose -f "./docker/docker-compose.e2e.yml" up -d
43+
44+
- name: Setup e2e test
45+
run: |
46+
cd apps/e2e
47+
yarn
48+
yarn playwright install
49+
50+
- name: Run e2e tests
51+
run: |
52+
npm run test:e2e

.github/workflows/test-on-pr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Run Tests On Pull Request
22

33
on:
44
pull_request:
5-
branches: [dev, beta, main]
5+
branches: [dev, main]
66

77
jobs:
88
test:

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@ volumes
4242

4343
dist
4444

45-
tsconfig.tsbuildinfo
45+
tsconfig.tsbuildinfo
46+
47+
# playwright
48+
playwright-report
49+
test-results
50+
user.json

apps/api/src/main.ts

+8
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,11 @@ async function bootstrap() {
7070
}
7171

7272
void bootstrap();
73+
74+
// setInterval(() => {
75+
// const { rss, heapTotal, heapUsed } = process.memoryUsage();
76+
// console.log('RSS:', (rss / 1024 / 1024).toFixed(2), 'MB');
77+
// console.log('Heap Total:', (heapTotal / 1024 / 1024).toFixed(2), 'MB');
78+
// console.log('Heap Used:', (heapUsed / 1024 / 1024).toFixed(2), 'MB');
79+
// console.log('------------------------');
80+
// }, 1000);

apps/e2e/database-utils.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import mysql from 'mysql2/promise';
2+
3+
export async function createConnection() {
4+
return await mysql.createConnection({
5+
host: '127.0.0.1',
6+
port: 13307,
7+
user: 'userfeedback',
8+
password: 'userfeedback',
9+
database: 'e2e',
10+
});
11+
}

apps/e2e/global.setup.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { expect, test as setup } from '@playwright/test';
2+
3+
const authFile = 'playwright/.auth/user.json';
4+
5+
setup('tenant create and authenticate', async ({ page }) => {
6+
await page.goto('http://localhost:3000/en/tenant/create');
7+
await page.getByLabel('Site name').click();
8+
await page.getByLabel('Site name').fill('TestTenant');
9+
await page.getByRole('button', { name: 'Setting', exact: true }).click();
10+
11+
await page.goto('http://localhost:3000/en/auth/sign-in');
12+
await expect(page.getByRole('banner')).toHaveText(/TestTenant/, {
13+
timeout: 500000,
14+
});
15+
await page.getByPlaceholder('ID').click();
16+
await page.getByPlaceholder('ID').fill('[email protected]');
17+
await page.getByPlaceholder('Password').click();
18+
await page.getByPlaceholder('Password').fill('12345678');
19+
await page.getByRole('button', { name: 'Sign In', exact: true }).click();
20+
await page.waitForURL('http://localhost:3000/en/main');
21+
22+
await page.context().storageState({ path: authFile });
23+
});

apps/e2e/global.teardown.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test as teardown } from '@playwright/test';
2+
3+
import { createConnection } from './database-utils';
4+
5+
export async function globalTeardown() {
6+
const connection = await createConnection();
7+
try {
8+
await connection.execute(`DELETE FROM tenant WHERE site_name = ?`, [
9+
'TestTenant',
10+
]);
11+
await connection.execute('ALTER TABLE tenant AUTO_INCREMENT = 1');
12+
await connection.execute('ALTER TABLE projects AUTO_INCREMENT = 1');
13+
await connection.execute('ALTER TABLE channels AUTO_INCREMENT = 1');
14+
await connection.execute('ALTER TABLE fields AUTO_INCREMENT = 1');
15+
await connection.execute('ALTER TABLE feedbacks AUTO_INCREMENT = 1');
16+
await connection.execute(`DELETE FROM users WHERE email = ?`, [
17+
18+
]);
19+
await connection.execute('ALTER TABLE users AUTO_INCREMENT = 1');
20+
await connection.execute('DELETE FROM histories');
21+
await connection.execute('ALTER TABLE histories AUTO_INCREMENT = 1');
22+
} finally {
23+
await connection.end();
24+
}
25+
}
26+
27+
teardown('teardown', async () => {
28+
try {
29+
await globalTeardown();
30+
console.log('Tearing down succeeds.');
31+
} catch (e) {
32+
console.log('Tearing down fails.', e);
33+
}
34+
});

apps/e2e/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "e2e",
3+
"version": "1.0.0",
4+
"description": "",
5+
"scripts": {
6+
"test:e2e": "playwright test"
7+
},
8+
"devDependencies": {
9+
"@playwright/test": "^1.40.1",
10+
"axios": "^1.6.3",
11+
"mysql2": "^3.6.5"
12+
}
13+
}

apps/e2e/playwright.config.ts

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import * as path from 'path';
2+
import { defineConfig, devices, PlaywrightTestConfig } from '@playwright/test';
3+
4+
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
5+
6+
/**
7+
* Read environment variables from file.
8+
* https://github.com/motdotla/dotenv
9+
*/
10+
// require('dotenv').config();
11+
12+
/**
13+
* See https://playwright.dev/docs/test-configuration.
14+
*/
15+
16+
export default defineConfig({
17+
testDir: '.',
18+
/* Maximum time one test can run for. */
19+
timeout: 30 * 1000,
20+
expect: {
21+
/**
22+
* Maximum time expect() should wait for the condition to be met.
23+
* For example in `await expect(locator).toHaveText();`
24+
*/
25+
timeout: 5000,
26+
},
27+
/* Run tests in files in parallel */
28+
fullyParallel: true,
29+
/* Fail the build on CI if you accidentally left test.only in the source code. */
30+
forbidOnly: !!process.env.CI,
31+
/* Retry on CI only */
32+
retries: process.env.CI ? 2 : 0,
33+
/* Opt out of parallel tests on CI. */
34+
workers: 1,
35+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
36+
reporter: 'html',
37+
testMatch: 'test.list.*',
38+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
39+
use: {
40+
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
41+
actionTimeout: 0,
42+
/* Base URL to use in actions like `await page.goto('/')`. */
43+
// baseURL: 'http://localhost:3000',
44+
45+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
46+
trace: 'on-first-retry',
47+
video: 'on',
48+
},
49+
/* Configure projects for major browsers */
50+
projects: [
51+
{
52+
name: 'global setup',
53+
testMatch: /global\.setup\.ts/,
54+
teardown: 'global teardown',
55+
},
56+
{
57+
name: 'global teardown',
58+
testMatch: /global\.teardown\.ts/,
59+
},
60+
{
61+
name: 'logged in chromium',
62+
use: { ...devices['Desktop Chrome'], storageState: STORAGE_STATE },
63+
dependencies: ['global setup'],
64+
},
65+
// {
66+
// name: 'logged out chromium',
67+
// testMatch: '**/sign-in*.spec.ts',
68+
// use: { ...devices['Desktop Chrome'] },
69+
// },
70+
// {
71+
// name: 'firefox',
72+
// use: { ...devices['Desktop Firefox'] },
73+
// },
74+
// {
75+
// name: 'webkit',
76+
// use: { ...devices['Desktop Safari'] },
77+
// },
78+
],
79+
80+
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
81+
outputDir: 'test-results/',
82+
83+
/* Run your local dev server before starting the tests */
84+
webServer: [
85+
{
86+
command: 'cd ../api && yarn start',
87+
port: 4000,
88+
reuseExistingServer: true,
89+
env: {
90+
MYSQL_PRIMARY_URL:
91+
'mysql://userfeedback:userfeedback@localhost:13307/e2e',
92+
MYSQL_SECONDARY_URLS:
93+
'["mysql://userfeedback:userfeedback@localhost:13307/e2e"]',
94+
AUTO_MIGRATION: 'true',
95+
},
96+
},
97+
{
98+
command: 'cd ../web && yarn build && yarn start',
99+
port: 3000,
100+
reuseExistingServer: true,
101+
env: {
102+
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:4000',
103+
API_BASE_URL: 'http://127.0.0.1:4000',
104+
},
105+
},
106+
],
107+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
export default () => {
4+
test.afterEach(async ({ page }) => {
5+
await page.getByText('TestProject').click();
6+
await page.getByText('FeedbackIssueSetting').hover();
7+
await page.getByRole('button', { name: 'Setting', exact: true }).click();
8+
9+
await page.getByText('Delete Project').click();
10+
await page.getByRole('button', { name: 'Delete' }).click();
11+
await page.getByPlaceholder('Please input').click();
12+
await page.getByPlaceholder('Please input').fill('TestProject');
13+
await page.getByRole('button', { name: 'Delete' }).click();
14+
await expect(page.getByText('TestProject')).toHaveCount(0);
15+
});
16+
17+
test('creating a project succeeds', async ({ page }) => {
18+
await page.goto('http://localhost:3000');
19+
await page.getByRole('button', { name: 'Create Project' }).click();
20+
await page.getByPlaceholder('Please enter Project Name.').click();
21+
await page
22+
.getByPlaceholder('Please enter Project Name.')
23+
.fill('TestProject');
24+
await page.getByPlaceholder('Please enter Project Description.').click();
25+
await page
26+
.getByPlaceholder('Please enter Project Description.')
27+
.fill('Project for test');
28+
await page.getByRole('button', { name: 'Next' }).click();
29+
await page.getByRole('button', { name: 'Next' }).click();
30+
await page.getByRole('button', { name: 'Next' }).click();
31+
await page.getByRole('button', { name: 'Next' }).click();
32+
await page.getByRole('button', { name: 'Complete' }).click();
33+
await expect(page.getByText('Project creation is complete.')).toBeVisible();
34+
await expect(page.locator('#Project\\ Name')).toHaveValue('TestProject');
35+
await expect(page.locator('#Project\\ Description')).toHaveValue(
36+
'Project for test',
37+
);
38+
await page.getByRole('button', { name: 'Next time' }).click();
39+
await expect(page.getByText('TestProject')).toBeVisible();
40+
});
41+
42+
test('creating a project with a new role succeeds', async ({ page }) => {
43+
await page.goto('http://localhost:3000');
44+
await page.getByRole('button', { name: 'Create Project' }).click();
45+
await page.getByPlaceholder('Please enter Project Name.').click();
46+
await page
47+
.getByPlaceholder('Please enter Project Name.')
48+
.fill('TestProject');
49+
await page.getByPlaceholder('Please enter Project Description.').click();
50+
await page
51+
.getByPlaceholder('Please enter Project Description.')
52+
.fill('Project for test');
53+
await page.getByRole('button', { name: 'Next' }).click();
54+
await page.getByRole('button', { name: 'Create Role' }).click();
55+
await page.getByLabel('Role Name').click();
56+
await page.getByLabel('Role Name').fill('Test Role');
57+
await page.getByRole('button', { name: 'OK' }).click();
58+
await page
59+
.getByRole('cell', { name: 'Test Role' })
60+
.getByRole('button')
61+
.click();
62+
await page.locator('li').filter({ hasText: 'Edit Role' }).click();
63+
await page
64+
.getByRole('row', { name: 'Read Feedback' })
65+
.getByRole('checkbox')
66+
.nth(3)
67+
.check();
68+
await page
69+
.getByRole('cell', { name: 'Test Role' })
70+
.getByRole('button')
71+
.nth(1)
72+
.click();
73+
await page.getByRole('button', { name: 'Next' }).click();
74+
await page.getByRole('button', { name: 'Next' }).click();
75+
await page.getByRole('button', { name: 'Next' }).click();
76+
await page.getByRole('button', { name: 'Complete' }).click();
77+
await expect(page.getByText('Project creation is complete.')).toBeVisible();
78+
await expect(page.locator('#Project\\ Name')).toHaveValue('TestProject');
79+
await expect(page.locator('#Project\\ Description')).toHaveValue(
80+
'Project for test',
81+
);
82+
await page
83+
.locator('div')
84+
.filter({ hasText: /^Role Management$/ })
85+
.getByRole('button')
86+
.click();
87+
await expect(page.getByText('Test Role')).toBeVisible();
88+
await expect(
89+
page
90+
.getByRole('row', { name: 'Read Feedback' })
91+
.getByRole('checkbox')
92+
.nth(3),
93+
).toBeChecked();
94+
await page.getByRole('button', { name: 'Next time' }).click();
95+
await expect(page.getByText('TestProject')).toBeVisible();
96+
});
97+
};

apps/e2e/scenarios/sign-up.spec.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)