mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Add e2e tests for login
This commit is contained in:
parent
31c32d5983
commit
7252290416
1
.github/workflows/frontend.yml
vendored
1
.github/workflows/frontend.yml
vendored
|
@ -42,6 +42,7 @@ jobs:
|
|||
run: |
|
||||
npm run lint
|
||||
npm test
|
||||
npm run teste2e
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
|
|
21
.vscode/launch.json
vendored
21
.vscode/launch.json
vendored
|
@ -46,9 +46,28 @@
|
|||
"args": ["test", "-k", "${fileBasenameNoExtension}"],
|
||||
"django": true
|
||||
},
|
||||
{
|
||||
// Run Tests for frontend for current file in Debug mode
|
||||
"name": "FE-DebugTestFile",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/rsconcept/frontend",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": [
|
||||
"playwright",
|
||||
"test",
|
||||
"${fileBasename}",
|
||||
"--headed",
|
||||
"--project=Desktop Chrome"
|
||||
],
|
||||
"env": {
|
||||
"PWDEBUG": "1"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
// Run Tests for frontned in Debug mode
|
||||
"name": "FE-DebugTestAll",
|
||||
"name": "Jest DebugAll",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${workspaceFolder}/rsconcept/frontend/node_modules/.bin/jest",
|
||||
|
|
|
@ -45,6 +45,9 @@ export default [
|
|||
},
|
||||
settings: { react: { version: 'detect' } },
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'require-jsdoc': 'off',
|
||||
|
||||
'react-compiler/react-compiler': 'error',
|
||||
'react-refresh/only-export-components': ['off', { allowConstantExport: true }],
|
||||
|
||||
|
@ -112,16 +115,12 @@ export default [
|
|||
rules: {
|
||||
...playwright.configs['flat/recommended'].rules,
|
||||
|
||||
'no-console': 'off',
|
||||
'require-jsdoc': 'off',
|
||||
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
'simple-import-sort/imports': 'warn'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'require-jsdoc': 'off'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -25,7 +25,7 @@ export default defineConfig({
|
|||
},
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
port: 3000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
});
|
||||
|
|
|
@ -36,13 +36,14 @@ export const NavigationState = ({ children }: React.PropsWithChildren) => {
|
|||
const router = useNavigate();
|
||||
|
||||
const [isBlocked, setIsBlocked] = useState(false);
|
||||
const [internalNavigation, setInternalNavigation] = useState(false);
|
||||
|
||||
function validate() {
|
||||
return !isBlocked || confirm('Изменения не сохранены. Вы уверены что хотите совершить переход?');
|
||||
}
|
||||
|
||||
function canBack() {
|
||||
return !!window.history && window.history?.length !== 0;
|
||||
return internalNavigation && !!window.history && window.history?.length !== 0;
|
||||
}
|
||||
|
||||
function push(props: NavigationProps) {
|
||||
|
@ -50,6 +51,7 @@ export const NavigationState = ({ children }: React.PropsWithChildren) => {
|
|||
window.open(`${props.path}`, '_blank');
|
||||
} else if (props.force || validate()) {
|
||||
setIsBlocked(false);
|
||||
setInternalNavigation(true);
|
||||
Promise.resolve(router(props.path, { viewTransition: true })).catch(console.error);
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +61,7 @@ export const NavigationState = ({ children }: React.PropsWithChildren) => {
|
|||
window.open(`${props.path}`, '_blank');
|
||||
} else if (props.force || validate()) {
|
||||
setIsBlocked(false);
|
||||
setInternalNavigation(true);
|
||||
return router(props.path, { viewTransition: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { authAnonymous } from './mocks/auth';
|
||||
import { expect, test } from './setup';
|
||||
|
||||
test('should load the homepage and display login button', async ({ page }) => {
|
||||
await authAnonymous(page);
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle('Концепт Портал');
|
||||
|
||||
|
|
25
rsconcept/frontend/tests/auth.spec.ts
Normal file
25
rsconcept/frontend/tests/auth.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { setupLogin } from './mocks/auth';
|
||||
import { expect, test } from './setup';
|
||||
|
||||
test('should display error message when login with wrong credentials', async ({ page }) => {
|
||||
await setupLogin(page);
|
||||
|
||||
await page.goto('/login');
|
||||
await page.getByRole('textbox', { name: 'Логин или email' }).fill('123');
|
||||
await page.getByRole('textbox', { name: 'Пароль' }).fill('123');
|
||||
await page.getByRole('button', { name: 'Войти' }).click();
|
||||
await expect(page.getByText('На Портале отсутствует такое сочетание имени пользователя и пароля')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Войти' })).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should login as admin successfully', async ({ page }) => {
|
||||
await setupLogin(page);
|
||||
|
||||
await page.goto('/login');
|
||||
await page.getByRole('textbox', { name: 'Логин или email' }).fill('admin');
|
||||
await page.getByRole('textbox', { name: 'Пароль' }).fill('password');
|
||||
await page.getByRole('button', { name: 'Войти' }).click();
|
||||
|
||||
await expect(page.getByText('Вы вошли в систему как admin')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Войти' })).toHaveCount(0);
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import { type Page } from '@playwright/test';
|
||||
|
||||
import { type ICurrentUser } from '../../src/features/auth/backend/types';
|
||||
import { BACKEND_URL } from '../constants';
|
||||
import { type ICurrentUser, IUserLoginDTO } from '../../src/features/auth/backend/types';
|
||||
import { BACKEND_URL } from './constants';
|
||||
|
||||
const dataAnonymousAuth: ICurrentUser = {
|
||||
id: null,
|
||||
|
@ -24,20 +24,50 @@ const dataUserAuth: ICurrentUser = {
|
|||
editor: [2]
|
||||
};
|
||||
|
||||
export async function authAnonymous(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: dataAnonymousAuth });
|
||||
let currentAuth = dataAnonymousAuth;
|
||||
|
||||
export async function setupAuth(context: Page) {
|
||||
await context.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: currentAuth });
|
||||
});
|
||||
}
|
||||
|
||||
export async function authAdmin(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: dataAdminAuth });
|
||||
export function authAnonymous() {
|
||||
currentAuth = dataAnonymousAuth;
|
||||
}
|
||||
|
||||
export function authAdmin() {
|
||||
currentAuth = dataAdminAuth;
|
||||
}
|
||||
|
||||
export function authUser() {
|
||||
currentAuth = dataUserAuth;
|
||||
}
|
||||
|
||||
export async function setupLogin(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/login`, async route => {
|
||||
const data = route.request().postDataJSON() as IUserLoginDTO;
|
||||
if (data.password !== 'password') {
|
||||
await route.fulfill({
|
||||
status: 400,
|
||||
json: {
|
||||
detail: 'Invalid credentials'
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (data.username === 'admin') {
|
||||
authAdmin();
|
||||
} else {
|
||||
authUser();
|
||||
}
|
||||
await route.fulfill({ status: 200 });
|
||||
});
|
||||
}
|
||||
|
||||
export async function authUser(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/auth`, async route => {
|
||||
await route.fulfill({ json: dataUserAuth });
|
||||
export async function setupLogout(page: Page) {
|
||||
await page.route(`${BACKEND_URL}/users/api/logout`, async route => {
|
||||
authAnonymous();
|
||||
await route.fulfill({ status: 200 });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ import {
|
|||
type IUserProfile,
|
||||
type IUserSignupDTO
|
||||
} from '../../src/features/users/backend/types';
|
||||
import { BACKEND_URL } from '../constants';
|
||||
import { BACKEND_URL } from './constants';
|
||||
|
||||
const dataActiveUsers: IUserInfo[] = [
|
||||
{
|
||||
id: 1,
|
||||
first_name: 'Admin',
|
||||
last_name: 'User'
|
||||
last_name: 'Admin'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -23,10 +23,10 @@ const dataActiveUsers: IUserInfo[] = [
|
|||
|
||||
let dataUserProfile: IUserProfile = {
|
||||
id: 1,
|
||||
username: 'user',
|
||||
email: 'user@example.com',
|
||||
first_name: 'User',
|
||||
last_name: 'User'
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
first_name: 'Admin',
|
||||
last_name: 'Admin'
|
||||
};
|
||||
|
||||
export async function setupUsers(page: Page) {
|
||||
|
|
14
rsconcept/frontend/tests/setup.ts
Normal file
14
rsconcept/frontend/tests/setup.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { test as base } from '@playwright/test';
|
||||
|
||||
import { setupAuth } from './mocks/auth';
|
||||
import { setupUsers } from './mocks/users';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
await setupAuth(page);
|
||||
await setupUsers(page);
|
||||
|
||||
await use(page);
|
||||
}
|
||||
});
|
|
@ -15,7 +15,7 @@
|
|||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"types": ["playwright"]
|
||||
"types": ["playwright", "node"]
|
||||
},
|
||||
"include": ["playwright.config.ts", "tests/**/*.ts"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user