import type { Page } from '@playwright/test'; import { createApi } from './api.js'; import { expect, test } from './baseFixtures.js'; import { adminuser, allaccessuser, stevenuser } from './defaults.js'; import { getRowFromCell, loadTab, navigate } from './helpers.js'; import { doCachedLogin } from './login.js'; import { setPluginState, setSettingState } from './settings.js'; /** * Adjust language and color settings * * TODO: Reimplement this - without logging out a cached user */ // test('Settings - Language / Color', async ({ browser }) => { // const page = await doCachedLogin(browser); // await page.getByRole('button', { name: 'Ally Access' }).click(); // await page.getByRole('menuitem', { name: 'Logout' }).click(); // await page.getByRole('button', { name: 'Send me an email' }).click(); // await page.getByLabel('Language toggle').click(); // await page.getByLabel('Select language').first().click(); // await page.getByRole('option', { name: 'German' }).click(); // await page.waitForTimeout(200); // await page.getByRole('button', { name: 'Benutzername und Passwort' }).click(); // await page.getByPlaceholder('Ihr Benutzername').click(); // await page.getByPlaceholder('Ihr Benutzername').fill('admin'); // await page.getByPlaceholder('Ihr Benutzername').press('Tab'); // await page.getByPlaceholder('Dein Passwort').fill('inventree'); // await page.getByRole('button', { name: 'Anmelden' }).click(); // await page.waitForTimeout(200); // await page.getByRole('tab', { name: 'Dashboard' }).click(); // await page.waitForURL('**/web/home'); // }); test('Settings - User theme', async ({ browser }) => { const page = await doCachedLogin(browser, { user: allaccessuser }); await page.waitForLoadState('networkidle'); await page.getByRole('button', { name: 'Ally Access' }).click(); await page.getByRole('menuitem', { name: 'User settings' }).click(); // loader await page.getByRole('textbox', { name: 'Loader Type Selector' }).click(); await page.getByRole('option', { name: 'Oval' }).click(); await page.getByRole('textbox', { name: 'Loader Type Selector' }).click(); await page.getByRole('option', { name: 'Bars' }).click(); // dark / light mode await page .getByRole('row', { name: 'Color Mode' }) .getByRole('button') .click(); await page .getByRole('row', { name: 'Color Mode' }) .getByRole('button') .click(); // colors await testColorPicker(page, 'Color Picker White'); await testColorPicker(page, 'Color Picker Black'); await page.waitForTimeout(500); await page.getByLabel('Reset Black Color').click(); await page.getByLabel('Reset White Color').click(); // radius await page .locator('div') .filter({ hasText: /^xssmmdlgxl$/ }) .nth(2) .click(); // primary await page.getByLabel('#fab005').click(); await page.getByLabel('#228be6').click(); }); test('Settings - User', async ({ browser }) => { const page = await doCachedLogin(browser, { user: allaccessuser, url: 'settings/user/' }); await loadTab(page, 'Account'); await page.getByText('Account Details').waitFor(); await page.getByText('Profile Details').waitFor(); // Language selection await page.getByRole('textbox', { name: 'Select language' }).click(); await page.getByRole('option', { name: 'العربية' }).waitFor(); await page.getByRole('option', { name: 'Deutsch' }).waitFor(); await page.getByRole('option', { name: 'English' }).waitFor(); await page.getByRole('option', { name: 'Español', exact: true }).waitFor(); await page.getByRole('option', { name: '日本語' }).waitFor(); await loadTab(page, 'Security'); await page.getByRole('button', { name: 'Single Sign On' }).waitFor(); await page.getByRole('button', { name: 'Access Tokens' }).waitFor(); await loadTab(page, 'Display Options'); await page .getByText('The navbar position is fixed to the top of the screen') .waitFor(); await page.getByText('Escape Key Closes Forms').waitFor(); await loadTab(page, 'Search'); await page.getByText('Whole Word Search').waitFor(); await page.getByText('Hide Unavailable Stock Items').waitFor(); await loadTab(page, 'Notifications'); await page .getByRole('button', { name: 'InvenTree Email Notifications' }) .waitFor(); await loadTab(page, 'Reporting'); await page.getByText('Inline report display').waitFor(); // Toggle boolean setting await page .getByLabel('setting-LABEL_INLINE-wrapper') .locator('span') .nth(1) .click(); await loadTab(page, 'Plugin Settings'); await page .getByRole('button', { name: 'InvenTree Email Notifications' }) .waitFor(); }); test('Settings - Global', async ({ browser }) => { const page = await doCachedLogin(browser, { user: stevenuser, url: 'settings/system/' }); // Ensure the "slack" notification plugin is enabled // This is to ensure it is visible in the "notification" settings tab await setPluginState({ plugin: 'inventree-slack-notification', state: true }); await loadTab(page, 'Server'); // edit some settings here await page .getByRole('button', { name: 'edit-setting-INVENTREE_COMPANY_NAME' }) .click(); await page .getByRole('textbox', { name: 'text-field-value' }) .fill('some data'); await page.getByRole('button', { name: 'Cancel' }).click(); // Toggle a boolean setting await page .getByLabel('setting-INVENTREE_ANNOUNCE_ID-wrapper') .locator('span') .nth(1) .click(); await page .getByText('Setting INVENTREE_ANNOUNCE_ID updated successfully') .waitFor(); await page .getByLabel('setting-INVENTREE_ANNOUNCE_ID-wrapper') .locator('span') .nth(1) .click(); await loadTab(page, 'Authentication'); await loadTab(page, 'Barcodes'); await loadTab(page, 'Pricing'); await loadTab(page, 'Parts'); await loadTab(page, 'Stock', true); await loadTab(page, 'Stock History'); await loadTab(page, 'Notifications'); await page .getByText( 'The settings below are specific to each available notification method' ) .waitFor(); await page .getByRole('button', { name: 'InvenTree Slack Notifications' }) .click(); await page.getByText('Slack incoming webhook url').waitFor(); await page .getByText('URL that is used to send messages to a slack channel') .waitFor(); await loadTab(page, 'Plugin Settings'); await page .getByText('The settings below are specific to each available plugin') .waitFor(); await page .getByRole('button', { name: 'InvenTree Barcodes Provides' }) .waitFor(); await page .getByRole('button', { name: 'InvenTree PDF label printer' }) .waitFor(); await page .getByRole('button', { name: 'InvenTree Slack Notifications' }) .waitFor(); }); test('Settings - Admin', async ({ browser }) => { // Note here we login with admin access const page = await doCachedLogin(browser, { user: adminuser }); // User settings await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('menuitem', { name: 'User settings' }).click(); await loadTab(page, 'Security'); await loadTab(page, 'Display Options'); await page.getByText('Date Format').waitFor(); await loadTab(page, 'Search'); await page.getByText('Regex Search').waitFor(); await loadTab(page, 'Notifications'); await loadTab(page, 'Reporting'); await page.getByText('Inline report display').waitFor(); // System Settings await page.locator('label').filter({ hasText: 'System Settings' }).click(); await page.getByText('Base URL', { exact: true }).waitFor(); await loadTab(page, 'Authentication'); await loadTab(page, 'Barcodes'); await loadTab(page, 'Notifications'); await loadTab(page, 'Pricing'); await loadTab(page, 'Labels'); await loadTab(page, 'Reporting'); await loadTab(page, 'Build Orders'); await loadTab(page, 'Purchase Orders'); await loadTab(page, 'Sales Orders'); await loadTab(page, 'Return Orders'); // Admin Center await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await loadTab(page, 'Background Tasks'); await loadTab(page, 'Error Reports'); await loadTab(page, 'Currencies'); await loadTab(page, 'Project Codes'); await loadTab(page, 'Custom Units'); await loadTab(page, 'Parameters', true); await loadTab(page, 'Category Parameters'); await loadTab(page, 'Label Templates'); await loadTab(page, 'Report Templates'); await loadTab(page, 'Plugins'); // Adjust some "location type" items await loadTab(page, 'Location Types'); // Edit first item ('Room') const roomCell = await page.getByRole('cell', { name: 'Room', exact: true }); const roomRow = await getRowFromCell(roomCell); await roomRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); await expect(page.getByLabel('text-field-name', { exact: true })).toHaveValue( 'Room' ); // Toggle the "description" field const oldDescription = await page .getByLabel('text-field-description', { exact: true }) .inputValue(); const newDescription = `${oldDescription} (edited)`; await page .getByLabel('text-field-description', { exact: true }) .fill(newDescription); await page.waitForTimeout(500); await page.getByRole('button', { name: 'Submit' }).click(); // Edit second item - 'Box (Large)' const boxCell = await page.getByRole('cell', { name: 'Box (Large)', exact: true }); const boxRow = await getRowFromCell(boxCell); await boxRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); await expect(page.getByLabel('text-field-name', { exact: true })).toHaveValue( 'Box (Large)' ); await expect( page.getByLabel('text-field-description', { exact: true }) ).toHaveValue('Large cardboard box'); await page.getByRole('button', { name: 'Cancel' }).click(); // Edit first item again (revert values) await roomRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); await page.getByLabel('text-field-name', { exact: true }).fill('Room'); await page.waitForTimeout(500); await page .getByLabel('text-field-description', { exact: true }) .fill(newDescription.replaceAll(' (edited)', '')); await page.waitForTimeout(500); await page.getByRole('button', { name: 'Submit' }).click(); }); test('Settings - Admin - Background Tasks', async ({ browser }) => { const page = await doCachedLogin(browser, { user: adminuser, url: 'settings/admin/background' }); // Background worker should be running, and idle await page.getByText('Background worker running').waitFor(); await page.getByText('Failed Tasks0').waitFor(); await page.getByText('Pending Tasks0').waitFor(); // Expand the "scheduled tasks" view await page.getByRole('button', { name: 'Scheduled Tasks' }).click(); // Check for some expected values await page .getByRole('cell', { name: 'InvenTree.tasks.delete_successful_tasks' }) .waitFor(); await page .getByRole('cell', { name: 'InvenTree.tasks.check_for_migrations' }) .waitFor(); }); test('Settings - Admin - Barcode History', async ({ browser }) => { // Login with admin credentials const page = await doCachedLogin(browser, { user: adminuser }); // Ensure that the "save scans" setting is enabled await setSettingState({ setting: 'BARCODE_STORE_RESULTS', value: true }); // Scan some barcodes (via API calls) const barcodes = ['ABC1234', 'XYZ5678', 'QRS9012']; const api = await createApi({}); for (let i = 0; i < barcodes.length; i++) { const barcode = barcodes[i]; let attempts = 5; while (attempts > 0) { let result = false; await api .post('barcode/', { data: { barcode: barcode }, timeout: 5000 }) .then((response) => { result = response.status() === 200; }); if (result) { break; } else { attempts -= 1; } } } await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await loadTab(page, 'Barcode Scans'); await page.waitForTimeout(500); // Barcode history is displayed in table barcodes.forEach(async (barcode) => { await page.getByText(barcode).first().waitFor(); }); }); test('Settings - Admin - Parameter', async ({ browser }) => { const page = await doCachedLogin(browser, { user: adminuser }); await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await loadTab(page, 'Parameters', true); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); // Clean old template data if exists await page .getByRole('cell', { name: 'my custom parameter', exact: true }) .waitFor({ timeout: 500 }) .then(async (cell) => { await page .getByRole('cell', { name: 'my custom parameter' }) .locator('..') .getByLabel('row-action-menu-') .click(); await page.getByRole('menuitem', { name: 'Delete' }).click(); await page.getByRole('button', { name: 'Delete' }).click(); }) .catch(() => {}); // Allow time for the table to load await page.getByRole('button', { name: 'Selection Lists' }).click(); await page.waitForLoadState('networkidle'); // Check for expected entry await page.getByRole('cell', { name: 'Animals', exact: true }).waitFor(); await page.getByText('Various animals and descriptions thereof').waitFor(); // Clean old list data if exists await page .getByRole('cell', { name: 'some list' }) .waitFor({ timeout: 500 }) .then(async (cell) => { await page .getByRole('cell', { name: 'some list' }) .locator('..') .getByLabel('row-action-menu-') .click(); await page.getByRole('menuitem', { name: 'Delete' }).click(); await page.getByRole('button', { name: 'Delete' }).click(); }) .catch(() => {}); // Add selection list await page.getByLabel('action-button-add-selection-').waitFor(); await page.getByLabel('action-button-add-selection-').click(); await page.getByLabel('text-field-name').fill('some list'); await page.getByLabel('text-field-description').fill('Listdescription'); // Add an entry to the selection list await page.getByRole('button', { name: 'action-button-add-new-row' }).click(); await page.getByRole('textbox', { name: 'text-field-value' }).fill('HW'); await page .getByRole('textbox', { name: 'text-field-label' }) .fill('Hardwood'); await page .getByRole('row', { name: 'boolean-field-active action-' }) .getByLabel('text-field-description') .fill('Hardwood materials'); await page.getByRole('cell', { name: 'boolean-field-active' }).click(); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('cell', { name: 'some list' }).waitFor(); await page.getByLabel('action-button-add-parameter').waitFor(); await page.getByLabel('action-button-add-parameter').click(); await page.getByLabel('text-field-name').fill('my custom parameter'); await page.getByLabel('text-field-description').fill('description'); await page .locator('div') .filter({ hasText: /^Search\.\.\.$/ }) .nth(2) .click(); await page .getByRole('option', { name: 'some list' }) .locator('div') .first() .click(); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('cell', { name: 'my custom parameter' }).click(); // Fill parameter await navigate(page, 'part/104/parameters/'); await page.getByLabel('Parameters').getByText('Parameters').waitFor(); await page.waitForLoadState('networkidle'); await page .getByRole('button', { name: 'action-menu-add-parameters' }) .click(); await page .getByRole('menuitem', { name: 'action-menu-add-parameters-create-parameter' }) .click(); await page.waitForTimeout(500); await page.getByText('Add Parameter').waitFor(); await page .getByText('Template *Parameter') .locator('div') .filter({ hasText: /^Search\.\.\.$/ }) .first() .click(); await page .getByText('Template *Parameter') .locator('div') .filter({ hasText: /^Search\.\.\.$/ }) .locator('input') .fill('my custom parameter'); await page.getByRole('option', { name: 'my custom parameter' }).click(); // Finally, select value from the SelectionList data await page.getByRole('combobox', { name: 'related-field-data' }).fill('wood'); await page .getByRole('option', { name: 'Hardwood Hardwood materials' }) .click(); await page.getByRole('button', { name: 'Submit' }).click(); // Check for the expected value await page.getByRole('cell', { name: 'HW', exact: true }).waitFor(); }); test('Settings - Admin - Unauthorized', async ({ browser }) => { // Try to access "admin" page with a non-staff user const page = await doCachedLogin(browser, { user: allaccessuser, url: 'settings/admin/' }); await page.waitForURL('**/settings/admin/**'); // Should get a permission denied message await page.getByText('Permission Denied').waitFor(); await page .getByRole('button', { name: 'Return to the index page' }) .waitFor(); // Try to access user settings page (should be accessible) await navigate(page, 'settings/user/'); await page.waitForURL('**/settings/user/**'); await loadTab(page, 'Display Options'); await loadTab(page, 'Account'); // Try to access global settings page await navigate(page, 'settings/system/'); await page.waitForURL('**/settings/system/**'); await page.getByText('Permission Denied').waitFor(); await page .getByRole('button', { name: 'Return to the index page' }) .waitFor(); }); // Test for user auth configuration test('Settings - Auth - Email', async ({ browser }) => { const page = await doCachedLogin(browser, { user: allaccessuser, url: 'settings/user/' }); await loadTab(page, 'Security'); await page.getByText('Currently no email addresses are registered').waitFor(); await page.getByLabel('email-address-input').fill('test-email@domain.org'); await page.getByLabel('email-address-submit').click(); await page.getByText('Unverified', { exact: true }).waitFor(); await page.getByLabel('test-email@domain.').click(); await page.getByRole('button', { name: 'Make Primary' }).click(); await page.getByText('Primary', { exact: true }).waitFor(); await page.getByRole('button', { name: 'Remove' }).click(); await page.getByText('Currently no email addresses are registered').waitFor(); }); async function testColorPicker(page: Page, ref: string) { const element = page.getByLabel(ref); await element.click(); const box = (await element.boundingBox())!; await page.mouse.click(box.x + box.width / 2, box.y + box.height + 25); await page.getByText('Color Mode').click(); } test('Settings - Auth - Tokens', async ({ browser }) => { const page = await doCachedLogin(browser, { user: allaccessuser, url: 'settings/user/' }); await page.getByRole('tab', { name: 'Security' }).click(); await page.getByRole('button', { name: 'Access Tokens' }).click(); await page .getByRole('button', { name: 'action-button-generate-token' }) .click(); await page .getByRole('textbox', { name: 'text-field-name' }) .fill('testtoken'); await page.getByRole('button', { name: 'Submit', exact: true }).click(); await page.getByText('Tokens are only shown once').waitFor(); await page .getByTestId('generated-api-token') .locator('.mantine-CloseButton-root') .click(); await page.getByRole('cell', { name: 'testtoken' }).waitFor(); });