Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6aeea4a
enhance e2e tests
jihun Oct 16, 2025
bbebb79
add database utils and clean code
jihun Oct 21, 2025
6bf195c
add license
jihun Oct 23, 2025
2ca1334
add create issue test
jihun Oct 23, 2025
ad8c20c
modify test description
jihun Oct 23, 2025
8e1a3b1
enable e2e-test workflow
jihun Oct 23, 2025
551a761
add create multiple feedbacks suite
jihun Oct 28, 2025
f7f108f
add date selector test
jihun Oct 28, 2025
82797c8
add search feedback case
jihun Oct 28, 2025
60f94a5
add test case
jihun Oct 28, 2025
f86b949
add test case
jihun Oct 29, 2025
cc9e90a
Merge branch 'dev' into feat/enhance-e2e-tests
jihun Oct 30, 2025
4543a67
fix lock file
jihun Oct 30, 2025
e87440c
fix test
jihun Oct 30, 2025
c10aaff
fix e2e test
jihun Oct 30, 2025
599941b
fix e2e test
jihun Oct 30, 2025
454dcc7
Merge branch 'dev' into feat/enhance-e2e-tests
jihun Oct 30, 2025
ea3fd96
fix e2e test
jihun Oct 30, 2025
d1e65bb
fix e2e test
jihun Oct 31, 2025
a756c2a
fix e2e test
jihun Oct 31, 2025
97691d6
fix test
jihun Oct 31, 2025
bec8d73
fix test
jihun Oct 31, 2025
10122b6
fix test
jihun Oct 31, 2025
9c0a8e4
fix test
jihun Oct 31, 2025
e807dc5
fix test
jihun Oct 31, 2025
594c4fe
fix test
jihun Oct 31, 2025
9b9bc26
fix: downgrade next version to ^15.5.5 and update renovate configurat…
chiol Oct 31, 2025
d2a5592
Merge pull request #2247 from line/fix/next-version
jihun Nov 3, 2025
989cc1c
Merge branch 'dev' into feat/enhance-e2e-tests
jihun Nov 3, 2025
ea2722b
Merge branch 'feat/enhance-e2e-tests' of https://github.com/line/abc-…
jihun Nov 3, 2025
0deccce
fix test
jihun Nov 3, 2025
d1d969f
fix test
jihun Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 36 additions & 35 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,44 @@ on:
jobs:
e2e-test:
runs-on: ubuntu-latest
# services:
# mysql:
# image: mysql:8.0.39
# env:
# MYSQL_ROOT_PASSWORD: userfeedback
# MYSQL_DATABASE: e2e
# MYSQL_USER: userfeedback
# MYSQL_PASSWORD: userfeedback
# TZ: UTC
# ports:
# - 13307:3306
# options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
# smtp:
# image: rnwood/smtp4dev:v3
# ports:
# - 5080:80
# - 25:25
# - 143:143
# opensearch:
# image: opensearchproject/opensearch:2.4.1
# ports:
# - 9200:9200
services:
mysql:
image: mysql:8.0.39
env:
MYSQL_ROOT_PASSWORD: userfeedback
MYSQL_DATABASE: e2e
MYSQL_USER: userfeedback
MYSQL_PASSWORD: userfeedback
TZ: UTC
ports:
- 13307:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
smtp:
image: rnwood/smtp4dev:v3
ports:
- 5080:80
- 25:25
- 143:143
opensearch:
image: opensearchproject/opensearch:2.4.1
ports:
- 9200:9200
Comment on lines 10 to 40
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Services are not connected to the application via environment variables.

The workflow defines MySQL, SMTP, and OpenSearch services, but doesn't configure environment variables for the application to discover and connect to them. The e2e test environment needs to know how to reach these services (e.g., database URL, SMTP host, search engine endpoint).

This is a critical gap that will likely cause tests to fail if they attempt to use these services.

Add an env: block at the job level (after runs-on:) to configure service connectivity:

     runs-on: ubuntu-latest
+    env:
+      DATABASE_URL: "mysql://userfeedback:userfeedback@localhost:13307/e2e"
+      OPENSEARCH_URL: "http://localhost:9200"
+      SMTP_HOST: "localhost"
+      SMTP_PORT: "25"
     services:

Adjust the values to match your application's expected environment variable names and service configuration.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/e2e-test.yml around lines 10 to 31: the job defines mysql,
smtp and opensearch services but doesn't expose any environment variables for
the application to discover them; add an env: block immediately after the job's
runs-on: line that sets the app's expected connection vars (for example
DATABASE_URL or MYSQL_HOST=127.0.0.1 and MYSQL_PORT=13307 plus MYSQL_USER and
MYSQL_PASSWORD, SMTP_HOST=127.0.0.1 and SMTP_PORT=25 or HTTP_PORT=5080 depending
on how your app connects, and OPENSEARCH_URL=http://127.0.0.1:9200) — adjust the
variable names/values to match your app’s expected env names and ports so the
e2e tests can connect to the services.

steps:
- name: Check out repository code
uses: actions/checkout@v4
# - name: Build and run
# run: |
# docker compose -f "./docker/docker-compose.e2e.yml" up -d
- name: Build and run
run: |
docker compose -f "./docker/docker-compose.e2e.yml" up -d

# - name: Setup e2e test
# run: |
# cd apps/e2e
# npm install -g corepack@latest
# pnpm install --frozen-lockfile
# pnpm playwright install
- name: Setup e2e test
run: |
cd apps/e2e
npm install -g corepack@latest
corepack enable
pnpm install --frozen-lockfile
pnpm playwright install

# - name: Run e2e tests
# run: |
# pnpm build
# pnpm test:e2e
- name: Run e2e tests
run: |
pnpm build
pnpm test:e2e
1 change: 0 additions & 1 deletion apps/api/src/domains/admin/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ describe('auth service ', () => {
const timeoutTime = await authService.sendEmailCode(dto);

expect(new Date(timeoutTime) > new Date()).toEqual(true);
expect(MockEmailVerificationMailingService.send).toHaveBeenCalledTimes(1);
});
it('sending a code by email succeeds with a duplicate email', async () => {
const duplicateEmail = emailFixture;
Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/domains/admin/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
BadRequestException,
Injectable,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
Expand Down Expand Up @@ -68,6 +69,7 @@ type UserProfileResponse = Record<string, string>;

@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
private REDIRECT_URI = `${process.env.BASE_URL}/auth/oauth-callback`;

constructor(
Expand Down Expand Up @@ -97,7 +99,14 @@ export class AuthService {
key: email,
});

await this.emailVerificationMailingService.send({ code, email });
// Skip email sending in development/test environment
if (process.env.NODE_ENV !== 'production') {
this.logger.warn(
`Skipping email sending for code: ${code}, email: ${email}`,
);
} else {
await this.emailVerificationMailingService.send({ code, email });
}

return DateTime.utc()
.plus({ seconds: 5 * 60 })
Expand Down
11 changes: 0 additions & 11 deletions apps/e2e/database-utils.ts

This file was deleted.

74 changes: 50 additions & 24 deletions apps/e2e/global.setup.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,80 @@
import { expect, test as setup } from "@playwright/test";

const authFile = "playwright/.auth/user.json";

setup("tenant create and authenticate", async ({ page }) => {
await page.goto("http://localhost:3000/tenant/create");
import { expect, test as setup } from '@playwright/test';

import { initializeDatabaseForTest } from './utils/database-utils';
import { initializeOpenSearchForTest } from './utils/opensearch-utils';

const authFile = 'playwright/.auth/user.json';

setup('tenant create and authenticate', async ({ page }) => {
try {
await initializeDatabaseForTest();
console.log('Database initialized for test');
} catch (error) {
console.warn(
'Database initialization failed, continuing without database cleanup:',
error,
);
}

try {
await initializeOpenSearchForTest();
console.log('OpenSearch initialized for test');
} catch (error) {
console.warn(
'OpenSearch initialization failed, continuing without OpenSearch:',
error,
);
}

await page.goto('http://localhost:3000/tenant/create');
await page.waitForTimeout(1000);

await page.locator("input[name='siteName']").click();
await page.locator("input[name='siteName']").fill("TestTenant");
await page.getByRole("button", { name: "Next", exact: true }).click();
await page.locator("input[name='siteName']").fill('TestTenant');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);

await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill("[email protected]");
await page.locator("input[name='email']").fill('[email protected]');

await page.getByRole("button", { name: "Request Code", exact: true }).click();
await page.getByRole('button', { name: 'Request Code', exact: true }).click();

await page.waitForSelector('input[name="code"]', { state: "visible" });
await page.waitForSelector('input[name="code"]', {
state: 'visible',
timeout: 60000,
});
await page.locator("input[name='code']").click();
await page.locator("input[name='code']").fill("000000");
await page.locator("input[name='code']").fill('000000');

await page.getByRole("button", { name: "Verify Code", exact: true }).click();
await page.getByRole('button', { name: 'Verify Code', exact: true }).click();

await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill("12345678!");
await page.locator("input[name='password']").fill('Abcd1234!');

await page.locator("input[name='confirmPassword']").click();
await page.locator("input[name='confirmPassword']").fill("12345678!");
await page.locator("input[name='confirmPassword']").fill('Abcd1234!');

await page.getByRole("button", { name: "Next", exact: true }).click();
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);

await page.getByRole("button", { name: "Confirm", exact: true }).click();
await page.getByRole('button', { name: 'Confirm', exact: true }).click();
Comment on lines 9 to +60
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace hard-coded credentials with environment variables.

Hard-coded credentials and a fixed verification code ('000000') pose security risks and reduce test portability. The fixed code suggests email verification is bypassed in tests.

Refactor to use environment variables:

   await page.locator("input[name='siteName']").click();
-  await page.locator("input[name='siteName']").fill('TestTenant');
+  await page.locator("input[name='siteName']").fill(process.env.TEST_TENANT_NAME || 'TestTenant');
   
   await page.locator("input[name='email']").click();
-  await page.locator("input[name='email']").fill('[email protected]');
+  await page.locator("input[name='email']").fill(process.env.TEST_USER_EMAIL || '[email protected]');
   
   await page.locator("input[name='code']").click();
-  await page.locator("input[name='code']").fill('000000');
+  await page.locator("input[name='code']").fill(process.env.TEST_VERIFICATION_CODE || '000000');
   
   await page.locator("input[name='password']").click();
-  await page.locator("input[name='password']").fill('Abcd1234!');
+  await page.locator("input[name='password']").fill(process.env.TEST_USER_PASSWORD || 'Abcd1234!');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await page.locator("input[name='siteName']").click();
await page.locator("input[name='siteName']").fill("TestTenant");
await page.getByRole("button", { name: "Next", exact: true }).click();
await page.locator("input[name='siteName']").fill('TestTenant');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill("[email protected]");
await page.locator("input[name='email']").fill('[email protected]');
await page.getByRole("button", { name: "Request Code", exact: true }).click();
await page.getByRole('button', { name: 'Request Code', exact: true }).click();
await page.waitForSelector('input[name="code"]', { state: "visible" });
await page.waitForSelector('input[name="code"]', {
state: 'visible',
timeout: 60000,
});
await page.locator("input[name='code']").click();
await page.locator("input[name='code']").fill("000000");
await page.locator("input[name='code']").fill('000000');
await page.getByRole("button", { name: "Verify Code", exact: true }).click();
await page.getByRole('button', { name: 'Verify Code', exact: true }).click();
await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill("12345678!");
await page.locator("input[name='password']").fill('Abcd1234!');
await page.locator("input[name='confirmPassword']").click();
await page.locator("input[name='confirmPassword']").fill("12345678!");
await page.locator("input[name='confirmPassword']").fill('Abcd1234!');
await page.getByRole("button", { name: "Next", exact: true }).click();
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole("button", { name: "Confirm", exact: true }).click();
await page.getByRole('button', { name: 'Confirm', exact: true }).click();
await page.locator("input[name='siteName']").click();
await page.locator("input[name='siteName']").fill(process.env.TEST_TENANT_NAME || 'TestTenant');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill(process.env.TEST_USER_EMAIL || '[email protected]');
await page.getByRole('button', { name: 'Request Code', exact: true }).click();
await page.waitForSelector('input[name="code"]', {
state: 'visible',
timeout: 60000,
});
await page.locator("input[name='code']").click();
await page.locator("input[name='code']").fill(process.env.TEST_VERIFICATION_CODE || '000000');
await page.getByRole('button', { name: 'Verify Code', exact: true }).click();
await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill(process.env.TEST_USER_PASSWORD || 'Abcd1234!');
await page.locator("input[name='confirmPassword']").click();
await page.locator("input[name='confirmPassword']").fill('Abcd1234!');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Confirm', exact: true }).click();

await page.waitForTimeout(1000);

await page.goto("http://localhost:3000/auth/sign-in");
await page.goto('http://localhost:3000/auth/sign-in');
await page.waitForTimeout(1000);

await expect(page.locator("body", { hasText: "TestTenant" })).toContainText(
"TestTenant"
await expect(page.locator('body', { hasText: 'TestTenant' })).toContainText(
'TestTenant',
);

await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill("[email protected]");
await page.locator("input[name='email']").fill('[email protected]');
await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill("12345678!");
await page.getByRole("button", { name: "Sign In", exact: true }).click();
await page.locator("input[name='password']").fill('Abcd1234!');
await page.getByRole('button', { name: 'Sign In', exact: true }).click();
await page.waitForTimeout(1000);

await page.waitForURL("http://localhost:3000/main/project/create");
await page.waitForURL('http://localhost:3000/main/project/create');

await page.context().storageState({ path: authFile });
});
38 changes: 14 additions & 24 deletions apps/e2e/global.teardown.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import { test as teardown } from '@playwright/test';

import { createConnection } from './database-utils';

export async function globalTeardown() {
const connection = await createConnection();
try {
await connection.execute(`DELETE FROM tenant WHERE site_name = ?`, [
'TestTenant',
]);
await connection.execute('ALTER TABLE tenant AUTO_INCREMENT = 1');
await connection.execute('ALTER TABLE projects AUTO_INCREMENT = 1');
await connection.execute('ALTER TABLE channels AUTO_INCREMENT = 1');
await connection.execute('ALTER TABLE fields AUTO_INCREMENT = 1');
await connection.execute('ALTER TABLE feedbacks AUTO_INCREMENT = 1');
await connection.execute(`DELETE FROM users WHERE email = ?`, [
'[email protected]',
]);
await connection.execute('ALTER TABLE users AUTO_INCREMENT = 1');
await connection.execute('DELETE FROM histories');
await connection.execute('ALTER TABLE histories AUTO_INCREMENT = 1');
} finally {
await connection.end();
}
}
import { cleanupDatabaseAfterTest } from './utils/database-utils';
import { cleanupOpenSearchAfterTest } from './utils/opensearch-utils';

teardown('teardown', async () => {
try {
await globalTeardown();
try {
await cleanupDatabaseAfterTest();
} catch (error) {
console.warn('Database cleanup failed:', error);
}

try {
await cleanupOpenSearchAfterTest();
} catch (error) {
console.warn('OpenSearch cleanup failed:', error);
}

console.log('Tearing down succeeds.');
} catch (e) {
console.log('Tearing down fails.', e);
Expand Down
1 change: 1 addition & 0 deletions apps/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test:e2e": "playwright test"
},
"devDependencies": {
"@opensearch-project/opensearch": "^3.5.1",
"@playwright/test": "^1.56.1",
"axios": "^1.13.1",
"mysql2": "^3.15.3"
Expand Down
Loading
Loading