-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(e2e): Improve e2e testing configuration and add more auth.spec.t…
…s and health.spec.ts
- Loading branch information
Showing
8 changed files
with
220 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Ignore temporary pid files | ||
*.pid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { spawn } from "child_process"; | ||
import { Process } from "e2e/process"; | ||
import * as http from 'http'; | ||
|
||
export const API_PID_FILE = 'api.pid'; | ||
export const CLIENT_PID_FILE = 'client.pid'; | ||
export const DEFAULT_START_TIMEOUT = 120_000; | ||
export const API_URL = 'http://localhost:4000'; | ||
export const CLIENT_URL = 'http://localhost:3000'; | ||
|
||
const waitUntilServerIsUp = (url: string) => { | ||
return new Promise<void>((resolve, reject) => { | ||
const check = () => { | ||
http.get(url, (res) => { | ||
if (res.statusCode === 200) { | ||
resolve(); | ||
} else { | ||
setTimeout(check, 300); | ||
} | ||
}).on('error', () => { | ||
setTimeout(check, 300); | ||
}); | ||
}; | ||
check(); | ||
}); | ||
}; | ||
|
||
const waitUntilServerIsDown = (url: string) => { | ||
return new Promise<void>((resolve) => { | ||
const check = () => { | ||
http.get(url, () => { | ||
console.log(`Server is still up. Waiting for it to shutdown... ${url}`); | ||
setTimeout(check, 300); | ||
}).on('error', () => { | ||
resolve(); | ||
}); | ||
}; | ||
check(); | ||
}); | ||
}; | ||
|
||
const startClientServer = async () => { | ||
const currentPid = Process.getPid(CLIENT_PID_FILE); | ||
if(currentPid !== undefined) return; | ||
|
||
const { pid } = spawn('NODE_ENV=test pnpm --filter client run build && NODE_ENV=test pnpm --filter client run start', { | ||
env: { ...process.env, NODE_ENV: 'test' }, | ||
stdio: 'ignore', | ||
detached: true, | ||
shell: true | ||
}); | ||
Process.setPid(CLIENT_PID_FILE, pid); | ||
await waitUntilServerIsUp(CLIENT_URL); | ||
} | ||
|
||
const stopClientServer = async () => { | ||
const currentPid = Process.getPid(CLIENT_PID_FILE); | ||
if(currentPid === undefined) return; | ||
|
||
Process.kill(-currentPid); | ||
Process.setPid(CLIENT_PID_FILE, undefined); | ||
} | ||
|
||
async function startAPIServer() { | ||
const currentPid = Process.getPid(API_PID_FILE); | ||
if(currentPid !== undefined) return; | ||
|
||
const { pid } = spawn('pnpm --filter api run start:prod', { | ||
env: { ...process.env, NODE_ENV: 'test' }, | ||
stdio: 'ignore', | ||
detached: true, | ||
shell: true | ||
}); | ||
Process.setPid(API_PID_FILE, pid); | ||
await waitUntilServerIsUp(API_URL); | ||
} | ||
|
||
async function stopAPIServer() { | ||
const currentPid = Process.getPid(API_PID_FILE); | ||
if(currentPid === undefined) return; | ||
|
||
Process.kill(-currentPid); | ||
Process.setPid(API_PID_FILE, undefined); | ||
await waitUntilServerIsDown(API_URL); | ||
} | ||
|
||
export const Application = { | ||
CLIENT_URL, | ||
API_URL, | ||
startClientServer, | ||
stopClientServer, | ||
startAPIServer, | ||
stopAPIServer, | ||
globalSetup: async () => { | ||
Process.setPid(API_PID_FILE, undefined); | ||
Process.setPid(CLIENT_PID_FILE, undefined); | ||
|
||
await Promise.race([ | ||
Promise.all([ | ||
Application.startAPIServer(), | ||
Application.startClientServer() | ||
]), | ||
new Promise((_, reject) => setTimeout(() => { | ||
reject(new Error('globalSetup timed out')) | ||
}, DEFAULT_START_TIMEOUT)), | ||
]) | ||
}, | ||
globalTeardown: async () => { | ||
await Promise.race([ | ||
Promise.all([ | ||
Application.stopAPIServer(), | ||
Application.stopClientServer() | ||
]), | ||
new Promise((_, reject) => setTimeout(() => { | ||
reject(new Error('globalTeardown timed out')) | ||
}, DEFAULT_START_TIMEOUT)), | ||
]) | ||
} | ||
} as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Application } from "e2e/application"; | ||
|
||
export default Application.globalSetup; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Application } from "e2e/application"; | ||
|
||
export default Application.globalTeardown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as fs from 'fs'; | ||
|
||
export const Process = { | ||
setPid: (path: string, pid?: number) => { | ||
if(pid === undefined) { | ||
if (fs.existsSync(path) === true) { | ||
fs.unlinkSync(path); | ||
} | ||
return; | ||
} | ||
fs.writeFileSync(path, pid.toString(), 'utf8'); | ||
}, | ||
getPid: (path: string) => { | ||
if (fs.existsSync(path) === false) { | ||
return undefined; | ||
} | ||
return parseInt(fs.readFileSync(path, 'utf8')); | ||
}, | ||
kill: (pid: number, signal: NodeJS.Signals = 'SIGKILL') => { | ||
try { | ||
process.kill(pid, signal); | ||
} catch (error) { | ||
if (error.code !== 'ESRCH') { | ||
throw error; | ||
} | ||
} | ||
} | ||
} as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,4 +37,15 @@ test.describe('Authentication', () => { | |
await page.waitForURL('/profile'); | ||
await expect(page.locator('input[type="email"]')).toHaveValue(user.email); | ||
}); | ||
|
||
test('a user tries to sign in with invalid credentials', async ({ page }) => { | ||
await page.goto('/auth/signin'); | ||
|
||
await page.getByPlaceholder('Enter your email').fill('[email protected]'); | ||
await page.getByPlaceholder('*******').fill('12345678'); | ||
await page.getByRole('button', { name: 'Log in' }).click(); | ||
|
||
const errorMessage = page.getByText('Invalid credentials'); | ||
await expect(errorMessage).toBeVisible(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { test, expect } from '@playwright/test'; | ||
import { E2eTestManager } from '@shared/lib/e2e-test-manager'; | ||
import { Application } from 'e2e/application'; | ||
|
||
test.describe('Health', () => { | ||
let testManager: E2eTestManager; | ||
|
||
test.beforeAll(async ({ browser }) => { | ||
const page = await browser.newPage(); | ||
testManager = await E2eTestManager.load(page); | ||
}); | ||
|
||
test.afterAll(async () => { | ||
await testManager.close(); | ||
}); | ||
|
||
test.describe('when the backend is up', () => { | ||
test('an anonymous user tries to check the application\'s health (both frontend and backend)', async ({ page }) => { | ||
// Given that backend application was succesfully started | ||
// App already started in globalSetup. Just being explicit here. | ||
await Application.startAPIServer(); | ||
|
||
// When | ||
const response = await page.goto('/health'); | ||
|
||
// Then | ||
expect(response?.status()).toBe(200); | ||
await expect(page.getByText('OK')).toBeVisible(); | ||
}); | ||
}) | ||
|
||
test.describe('when the backend is down', () => { | ||
test.afterAll(async () => { | ||
await Application.startAPIServer(); | ||
}) | ||
|
||
test('an anonymous user tries to check the application\'s health (both frontend and backend)', async ({ page }) => { | ||
// Given that the backend app is unavailable | ||
await Application.stopAPIServer(); | ||
|
||
// When | ||
const response = await page.goto('/health'); | ||
|
||
// Then | ||
expect(response?.status()).toBe(503); | ||
await expect(page.getByText('KO')).toBeVisible(); | ||
}); | ||
}) | ||
}); |