mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-07 21:33:18 +01:00
Add basic e2e tests for admin panel
This commit is contained in:
committed by
Constantin Graf
parent
a12ee1b029
commit
06a2048771
1
.env.ci
1
.env.ci
@@ -6,6 +6,7 @@ APP_DEBUG=true
|
|||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_FORCE_HTTPS=false
|
APP_FORCE_HTTPS=false
|
||||||
APP_ENABLE_REGISTRATION=true
|
APP_ENABLE_REGISTRATION=true
|
||||||
|
SUPER_ADMINS=admin@example.com
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
|
|||||||
75
e2e/admin-panel.spec.ts
Normal file
75
e2e/admin-panel.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { adminTest as test, expect } from '../playwright/fixtures';
|
||||||
|
import { PLAYWRIGHT_BASE_URL, TEST_USER_PASSWORD } from '../playwright/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index pages of all Filament resources that ship with the core application.
|
||||||
|
* Resources provided by extensions are intentionally left out, because
|
||||||
|
* extensions are not installed in CI.
|
||||||
|
*/
|
||||||
|
const resourcePages = [
|
||||||
|
{ path: '/admin/time-entries', heading: 'Time Entries' },
|
||||||
|
{ path: '/admin/projects', heading: 'Projects' },
|
||||||
|
{ path: '/admin/tasks', heading: 'Tasks' },
|
||||||
|
{ path: '/admin/clients', heading: 'Clients' },
|
||||||
|
{ path: '/admin/tags', heading: 'Tags' },
|
||||||
|
{ path: '/admin/reports', heading: 'Reports' },
|
||||||
|
{ path: '/admin/users', heading: 'Users' },
|
||||||
|
{ path: '/admin/organizations', heading: 'Organizations' },
|
||||||
|
{ path: '/admin/organization-invitations', heading: 'Invitations' },
|
||||||
|
{ path: '/admin/project-members', heading: 'Project Members' },
|
||||||
|
{ path: '/admin/tokens', heading: 'Tokens' },
|
||||||
|
{ path: '/admin/failed-jobs', heading: 'Failed Jobs' },
|
||||||
|
{ path: '/admin/audits', heading: 'Audits' },
|
||||||
|
];
|
||||||
|
|
||||||
|
test.describe('Admin Panel Access', () => {
|
||||||
|
test.use({ storageState: undefined });
|
||||||
|
|
||||||
|
test('test that the admin panel redirects guests to the login page', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/admin');
|
||||||
|
await expect(page).toHaveURL(PLAYWRIGHT_BASE_URL + '/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test that the admin panel is forbidden for users that are no super admin', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const email = `john+${Date.now()}_${Math.floor(Math.random() * 10000)}@doe.com`;
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/register');
|
||||||
|
await page.getByLabel('Name').fill('John Doe');
|
||||||
|
await page.getByLabel('Email').fill(email);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(TEST_USER_PASSWORD);
|
||||||
|
await page.getByLabel('Confirm Password').fill(TEST_USER_PASSWORD);
|
||||||
|
await page.getByLabel('I agree to the Terms of').click();
|
||||||
|
await page.getByRole('button', { name: 'Register' }).click();
|
||||||
|
await expect(page.getByTestId('dashboard_view')).toBeVisible();
|
||||||
|
|
||||||
|
const response = await page.goto(PLAYWRIGHT_BASE_URL + '/admin');
|
||||||
|
expect(response?.status()).toBe(403);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Admin Panel', () => {
|
||||||
|
test('test that the admin dashboard loads with all widgets', async ({ page }) => {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/admin');
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: 'Dashboard', level: 1 })).toBeVisible();
|
||||||
|
|
||||||
|
// The widgets are loaded lazily via Livewire, so they only show up once
|
||||||
|
// their content has been rendered.
|
||||||
|
await expect(page.getByText('Total real users')).toBeVisible();
|
||||||
|
await expect(page.getByText('Placeholder users')).toBeVisible();
|
||||||
|
await expect(page.getByText('Active users in the last seven days')).toBeVisible();
|
||||||
|
await expect(page.getByText('User Registrations', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('Time Entries Created', { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText('Time Entries Imported', { exact: true })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const { path, heading } of resourcePages) {
|
||||||
|
test(`test that the admin page ${path} loads`, async ({ page }) => {
|
||||||
|
const response = await page.goto(PLAYWRIGHT_BASE_URL + path);
|
||||||
|
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
await expect(page.getByRole('heading', { name: heading, level: 1 })).toBeVisible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
24
e2e/utils/admin.ts
Normal file
24
e2e/utils/admin.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
import { PLAYWRIGHT_BASE_URL } from '../../playwright/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The seeded user that is allowed to access the Filament admin panel.
|
||||||
|
*
|
||||||
|
* Panel access is granted by email address via the `SUPER_ADMINS` config, so
|
||||||
|
* the admin panel tests cannot use the throwaway users created by the default
|
||||||
|
* fixture and rely on the seeded user instead.
|
||||||
|
*/
|
||||||
|
export const SUPER_ADMIN_EMAIL = 'admin@example.com';
|
||||||
|
export const SEEDED_USER_PASSWORD = 'password';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in as the seeded super admin user via the regular application login.
|
||||||
|
*/
|
||||||
|
export async function loginAsSuperAdmin(page: Page): Promise<void> {
|
||||||
|
await page.goto(PLAYWRIGHT_BASE_URL + '/login');
|
||||||
|
await page.getByLabel('Email').fill(SUPER_ADMIN_EMAIL);
|
||||||
|
await page.getByLabel('Password').fill(SEEDED_USER_PASSWORD);
|
||||||
|
await page.getByRole('button', { name: 'Log in' }).click();
|
||||||
|
await expect(page.getByTestId('dashboard_view')).toBeVisible({ timeout: 15000 });
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
import { test as baseTest } from '@playwright/test';
|
import { test as baseTest } from '@playwright/test';
|
||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
import { PLAYWRIGHT_BASE_URL, TEST_USER_PASSWORD } from './config';
|
import { PLAYWRIGHT_BASE_URL, TEST_USER_PASSWORD } from './config';
|
||||||
import { type TestContext, setupTestContext } from '../e2e/utils/api';
|
import { type TestContext, setupTestContext } from '../e2e/utils/api';
|
||||||
import { setupAdminUser, setupEmployeeUser } from '../e2e/utils/members';
|
import { setupAdminUser, setupEmployeeUser } from '../e2e/utils/members';
|
||||||
|
import { loginAsSuperAdmin } from '../e2e/utils/admin';
|
||||||
|
|
||||||
export * from '@playwright/test';
|
export * from '@playwright/test';
|
||||||
export type { TestContext };
|
export type { TestContext };
|
||||||
@@ -116,3 +119,33 @@ export const test = baseTest.extend<
|
|||||||
await closeAdmin();
|
await closeAdmin();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication fixture for the Filament admin panel.
|
||||||
|
*
|
||||||
|
* The admin panel can only be accessed by the seeded super admin user, so the
|
||||||
|
* throwaway users of the default fixture cannot be used. Logging in is rate
|
||||||
|
* limited (5 attempts per minute and email address), therefore the session is
|
||||||
|
* created once per worker and reused by all tests of that worker.
|
||||||
|
*/
|
||||||
|
export const adminTest = baseTest.extend<object, { adminStorageState: string }>({
|
||||||
|
adminStorageState: [
|
||||||
|
async ({ browser }, use, workerInfo) => {
|
||||||
|
const fileName = path.resolve(
|
||||||
|
workerInfo.project.outputDir,
|
||||||
|
`.auth/admin-${workerInfo.workerIndex}.json`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(fileName)) {
|
||||||
|
const context = await browser.newContext({ storageState: undefined });
|
||||||
|
await loginAsSuperAdmin(await context.newPage());
|
||||||
|
await context.storageState({ path: fileName });
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await use(fileName);
|
||||||
|
},
|
||||||
|
{ scope: 'worker' },
|
||||||
|
],
|
||||||
|
storageState: ({ adminStorageState }, use) => use(adminStorageState),
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user