mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-27 19:16:44 +00:00
[CI] Playwright improvements (#9395)
* Allow port 4173 (vite preview) * Change 'base' attr based on vite command * Allow api_host to be specified separately * Harden API host functionality * Adjust server selections * Cleanup vite.config.ts * Adjust playwright configuration - Allow to run in "production" mode - Builds the code first - Runs only the backend web server - Not suitable for coverage * Tweak github actions * Tweak QC file * Reduce number of steps * Tweak CI file * Fix typo * Ensure translation before build * Fix hard-coded test * Test tweaks * uncomment * Revert some changes * Run with gunicorn, single worker * Reduce log output in DEBUG mode * Update deps * Add global-setup func * Fix for .gitignore file * Cached auth state * Tweak login func * Updated tests * Enable parallel workers again * Simplify config * Try with a single worker again * Single retry mode * Run auth setup first - Prevent issues with parallel test doing login * Improve test setup process * Tweaks * Bump to 3 workers * Tweak playwright settings * Revert change * Revert change
This commit is contained in:
parent
858eb8f807
commit
7f5a447769
28
.github/workflows/qc_checks.yaml
vendored
28
.github/workflows/qc_checks.yaml
vendored
@ -554,7 +554,7 @@ jobs:
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
|
||||
platform_ui:
|
||||
web_ui:
|
||||
name: Tests - Web UI
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
@ -584,7 +584,6 @@ jobs:
|
||||
INVENTREE_DB_PASSWORD: inventree_password
|
||||
INVENTREE_DEBUG: true
|
||||
INVENTREE_PLUGINS_ENABLED: false
|
||||
VITE_COVERAGE: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
|
||||
@ -599,13 +598,19 @@ jobs:
|
||||
apt-dependency: postgresql-client libpq-dev
|
||||
pip-dependency: psycopg2
|
||||
- name: Set up test data
|
||||
run: invoke dev.setup-test -iv
|
||||
- name: Rebuild thumbnails
|
||||
run: invoke int.rebuild-thumbnails
|
||||
run: |
|
||||
invoke dev.setup-test -iv
|
||||
invoke int.rebuild-thumbnails
|
||||
- name: Install dependencies
|
||||
run: invoke int.frontend-compile
|
||||
- name: Install Playwright Browsers
|
||||
run: cd src/frontend && npx playwright install --with-deps
|
||||
run: |
|
||||
invoke int.frontend-compile
|
||||
cd src/frontend && npx playwright install --with-deps
|
||||
- name: Set Production Mode
|
||||
if: github.event_name == 'pull_request'
|
||||
run: echo "VITE_PRODUCTION_BUILD=true" >> $GITHUB_ENV
|
||||
- name: Set Coverage Mode
|
||||
if: github.event_name != 'pull_request'
|
||||
run: echo "VITE_COVERAGE_BUILD=true" >> $GITHUB_ENV
|
||||
- name: Run Playwright tests
|
||||
id: tests
|
||||
run: cd src/frontend && npx nyc playwright test
|
||||
@ -616,16 +621,17 @@ jobs:
|
||||
path: src/frontend/playwright-report/
|
||||
retention-days: 14
|
||||
- name: Report coverage
|
||||
if: always()
|
||||
if: github.event_name != 'pull_request'
|
||||
run: cd src/frontend && npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --exclude-after-remap false
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # pin@v5.4.0
|
||||
if: always()
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
slug: inventree/InvenTree
|
||||
flags: web
|
||||
- name: Upload bundler info
|
||||
if: github.event_name != 'pull_request'
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
@ -633,7 +639,7 @@ jobs:
|
||||
yarn install
|
||||
yarn run build
|
||||
|
||||
platform_ui_build:
|
||||
web_ui_build:
|
||||
name: Build - Web UI
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
|
@ -406,13 +406,22 @@ def get_frontend_settings(debug=True):
|
||||
'INVENTREE_FRONTEND_SETTINGS', 'frontend_settings', {}, typecast=dict
|
||||
)
|
||||
|
||||
# Set the base URL
|
||||
# Set the base URL for the user interface
|
||||
# This is the UI path e.g. '/web/'
|
||||
if 'base_url' not in frontend_settings:
|
||||
frontend_settings['base_url'] = (
|
||||
get_setting('INVENTREE_FRONTEND_URL_BASE', 'frontend_url_base', 'web')
|
||||
or 'web'
|
||||
)
|
||||
|
||||
# If provided, specify the API host
|
||||
api_host = frontend_settings.get('api_host', None) or get_setting(
|
||||
'INVENTREE_FRONTEND_API_HOST', 'frontend_api_host', None
|
||||
)
|
||||
|
||||
if api_host:
|
||||
frontend_settings['api_host'] = api_host
|
||||
|
||||
# Set the server list
|
||||
frontend_settings['server_list'] = frontend_settings.get('server_list', [])
|
||||
|
||||
|
@ -1088,6 +1088,7 @@ if DEBUG:
|
||||
'http://localhost',
|
||||
'http://*.localhost',
|
||||
'http://*localhost:8000',
|
||||
'http://*localhost:4173',
|
||||
'http://*localhost:5173',
|
||||
]:
|
||||
if origin not in CSRF_TRUSTED_ORIGINS:
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
import structlog
|
||||
@ -60,13 +61,16 @@ def check_system_health(**kwargs):
|
||||
|
||||
if not is_worker_running(**kwargs): # pragma: no cover
|
||||
result = False
|
||||
logger.warning('Background worker check failed')
|
||||
if not settings.DEBUG:
|
||||
logger.warning('Background worker check failed')
|
||||
|
||||
if not InvenTree.helpers_email.is_email_configured(): # pragma: no cover
|
||||
result = False
|
||||
logger.warning('Email backend not configured')
|
||||
if not settings.DEBUG:
|
||||
logger.warning('Email backend not configured')
|
||||
|
||||
if not result: # pragma: no cover
|
||||
logger.warning('InvenTree system health checks failed')
|
||||
if not settings.DEBUG:
|
||||
logger.warning('InvenTree system health checks failed')
|
||||
|
||||
return result
|
||||
|
@ -41,6 +41,7 @@
|
||||
"@mantine/notifications": "^7.16.0",
|
||||
"@mantine/spotlight": "^7.16.0",
|
||||
"@mantine/vanilla-extract": "^7.16.0",
|
||||
"@messageformat/date-skeleton": "^1.1.0",
|
||||
"@sentry/react": "^8.43.0",
|
||||
"@tabler/icons-react": "^3.17.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
@ -83,7 +84,7 @@
|
||||
"@lingui/cli": "^5.3.0",
|
||||
"@lingui/macro": "^5.3.0",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@types/node": "^22.6.0",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.3.8",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
@ -94,6 +95,7 @@
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"nyc": "^17.1.0",
|
||||
"path": "^0.12.7",
|
||||
"rollup": "^4.0.0",
|
||||
"rollup-plugin-license": "^3.5.3",
|
||||
"typescript": "^5.8.2",
|
||||
|
@ -1,13 +1,96 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import type TestConfigWebServer from '@playwright/test';
|
||||
|
||||
// Detect if running in CI
|
||||
const IS_CI = !!process.env.CI;
|
||||
const IS_COVERAGE = !!process.env.VITE_COVERAGE_BUILD;
|
||||
|
||||
// If specified, tests will be run against the production build
|
||||
const IS_PRODUCTION = !!process.env.VITE_PRODUCTION_BUILD;
|
||||
|
||||
console.log('Running Playwright tests:');
|
||||
console.log(` - CI Mode: ${IS_CI}`);
|
||||
console.log(` - Coverage Mode: ${IS_COVERAGE}`);
|
||||
console.log(` - Production Mode: ${IS_PRODUCTION}`);
|
||||
|
||||
const MAX_WORKERS: number = 3;
|
||||
const MAX_RETRIES: number = 3;
|
||||
|
||||
/* We optionally spin-up services based on the testing mode:
|
||||
*
|
||||
* Local Development:
|
||||
* - If running locally (developer mode), we run "vite dev" with HMR enabled
|
||||
* - This allows playwright to monitor the code for changes
|
||||
* - WORKERS = 1 (to avoid conflicts with HMR)
|
||||
*
|
||||
* CI Mode (Production):
|
||||
* - In CI (GitHub actions), we run "vite build" to generate a production build
|
||||
* - This build is then served by a local server for testing
|
||||
* - This allows the tests to run much faster and with parallel workers
|
||||
* - Run a Gunicorn multi-threaded web server to handle multiple requests
|
||||
* - WORKERS = MAX_WORKERS (to speed up the tests)
|
||||
*
|
||||
* CI Mode (Coverage):
|
||||
* - In coverage mode (GitHub actions), we cannot compile the code first
|
||||
* - This is because we need to compile coverage report against ./src directory
|
||||
* - So, we run "vite dev" with coverage enabled (similar to the local dev mode)
|
||||
* - WORKERS = 1 (to avoid conflicts with HMR)
|
||||
*/
|
||||
|
||||
const devServer: TestConfigWebServer = {
|
||||
command: 'yarn run dev --host --port 5173',
|
||||
url: 'http://localhost:5173',
|
||||
reuseExistingServer: IS_CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
timeout: 120 * 1000
|
||||
};
|
||||
|
||||
const WEB_BUILD_CMD: string =
|
||||
'yarn run extract && yarn run compile && yarn run build';
|
||||
|
||||
// Command to spin-up the backend server
|
||||
// In production mode, we want a stronger webserver to handle multiple requests
|
||||
const WEB_SERVER_CMD: string = IS_PRODUCTION
|
||||
? `${WEB_BUILD_CMD} && gunicorn --chdir ../backend/InvenTree --workers 8 --thread 8 --bind 127.0.0.1:8000 InvenTree.wsgi`
|
||||
: 'invoke dev.server -a 127.0.0.1:8000';
|
||||
|
||||
const webServer: TestConfigWebServer = {
|
||||
// If running in production mode, we need to build the frontend first
|
||||
command: WEB_SERVER_CMD,
|
||||
env: {
|
||||
INVENTREE_DEBUG: 'True',
|
||||
INVENTREE_PLUGINS_ENABLED: 'True',
|
||||
INVENTREE_ADMIN_URL: 'test-admin',
|
||||
INVENTREE_SITE_URL: 'http://localhost:8000',
|
||||
INVENTREE_FRONTEND_API_HOST: 'http://localhost:8000',
|
||||
INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
|
||||
INVENTREE_COOKIE_SAMESITE: 'Lax',
|
||||
INVENTREE_LOGIN_ATTEMPTS: '100'
|
||||
},
|
||||
url: 'http://localhost:8000/api/',
|
||||
reuseExistingServer: IS_CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
timeout: 120 * 1000
|
||||
};
|
||||
|
||||
const serverList: TestConfigWebServer[] = [];
|
||||
|
||||
if (!IS_PRODUCTION) {
|
||||
serverList.push(devServer);
|
||||
}
|
||||
|
||||
serverList.push(webServer);
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
fullyParallel: true,
|
||||
fullyParallel: false,
|
||||
timeout: 90000,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 3 : 0,
|
||||
workers: 1, // process.env.CI ? 3 : undefined,
|
||||
reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
|
||||
forbidOnly: !!IS_CI,
|
||||
retries: IS_CI ? MAX_RETRIES : 0,
|
||||
workers: IS_PRODUCTION ? MAX_WORKERS : 1,
|
||||
reporter: IS_CI ? [['html', { open: 'never' }], ['github']] : 'list',
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
@ -26,35 +109,11 @@ export default defineConfig({
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: 'yarn run dev --host',
|
||||
url: 'http://localhost:5173',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
timeout: 120 * 1000
|
||||
},
|
||||
{
|
||||
command: 'invoke dev.server -a 127.0.0.1:8000',
|
||||
env: {
|
||||
INVENTREE_DEBUG: 'True',
|
||||
INVENTREE_PLUGINS_ENABLED: 'True',
|
||||
INVENTREE_ADMIN_URL: 'test-admin',
|
||||
INVENTREE_SITE_URL: 'http://localhost:8000',
|
||||
INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
|
||||
INVENTREE_COOKIE_SAMESITE: 'Lax',
|
||||
INVENTREE_LOGIN_ATTEMPTS: '100'
|
||||
},
|
||||
url: 'http://127.0.0.1:8000/api/',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
timeout: 120 * 1000
|
||||
}
|
||||
],
|
||||
webServer: serverList,
|
||||
globalSetup: './playwright/global-setup.ts',
|
||||
use: {
|
||||
baseURL: 'http://localhost:5173',
|
||||
baseURL: IS_PRODUCTION ? 'http://localhost:8000' : 'http://localhost:5173',
|
||||
headless: IS_PRODUCTION ? true : undefined,
|
||||
trace: 'on-first-retry'
|
||||
}
|
||||
});
|
||||
|
3
src/frontend/playwright/.gitignore
vendored
Normal file
3
src/frontend/playwright/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Ignore auth cache
|
||||
auth/
|
||||
*.json
|
45
src/frontend/playwright/global-setup.ts
Normal file
45
src/frontend/playwright/global-setup.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { type FullConfig, chromium } from '@playwright/test';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { doCachedLogin } from '../tests/login';
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const authDir = path.resolve('./playwright/auth');
|
||||
|
||||
if (fs.existsSync(authDir)) {
|
||||
// Clear out the cached authentication states
|
||||
fs.rm(path.resolve('./playwright/auth'), { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
console.error('Failed to clear out cached authentication states:', err);
|
||||
} else {
|
||||
console.log('Removed cached authentication states');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Perform login for each user
|
||||
const browser = await chromium.launch();
|
||||
|
||||
await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
await doCachedLogin(browser, {
|
||||
username: 'allaccess',
|
||||
password: 'nolimits'
|
||||
});
|
||||
|
||||
await doCachedLogin(browser, {
|
||||
username: 'reader',
|
||||
password: 'readonly'
|
||||
});
|
||||
|
||||
await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff'
|
||||
});
|
||||
}
|
||||
|
||||
export default globalSetup;
|
@ -10,9 +10,9 @@ export const api = axios.create({});
|
||||
* Setup default settings for the Axios API instance.
|
||||
*/
|
||||
export function setApiDefaults() {
|
||||
const { host } = useLocalState.getState();
|
||||
const { getHost } = useLocalState.getState();
|
||||
|
||||
api.defaults.baseURL = host;
|
||||
api.defaults.baseURL = getHost();
|
||||
api.defaults.timeout = 5000;
|
||||
|
||||
api.defaults.withCredentials = true;
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { ActionIcon } from '@mantine/core';
|
||||
import { IconDeviceFloppy, IconEdit } from '@tabler/icons-react';
|
||||
|
||||
export function EditButton({
|
||||
setEditing,
|
||||
editing,
|
||||
disabled,
|
||||
saveIcon
|
||||
}: Readonly<{
|
||||
setEditing: (value?: React.SetStateAction<boolean> | undefined) => void;
|
||||
editing: boolean;
|
||||
disabled?: boolean;
|
||||
saveIcon?: JSX.Element;
|
||||
}>) {
|
||||
saveIcon = saveIcon || <IconDeviceFloppy />;
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() => setEditing()}
|
||||
disabled={disabled}
|
||||
variant='default'
|
||||
>
|
||||
{editing ? saveIcon : <IconEdit />}
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
@ -1,10 +1,19 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { ActionIcon, Divider, Group, Select, Table, Text } from '@mantine/core';
|
||||
import {
|
||||
ActionIcon,
|
||||
Divider,
|
||||
Group,
|
||||
Select,
|
||||
Table,
|
||||
Text,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useToggle } from '@mantine/hooks';
|
||||
import {
|
||||
IconApi,
|
||||
IconCheck,
|
||||
IconCircleCheck,
|
||||
IconEdit,
|
||||
IconInfoCircle,
|
||||
IconPlugConnected,
|
||||
IconServer,
|
||||
@ -15,7 +24,7 @@ import { Wrapper } from '../../pages/Auth/Layout';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import type { HostList } from '../../states/states';
|
||||
import { EditButton } from '../buttons/EditButton';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { HostOptionsForm } from './HostOptionsForm';
|
||||
|
||||
export function InstanceOptions({
|
||||
@ -27,7 +36,7 @@ export function InstanceOptions({
|
||||
ChangeHost: (newHost: string | null) => void;
|
||||
setHostEdit: () => void;
|
||||
}>) {
|
||||
const [HostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
||||
const [hostListEdit, setHostListEdit] = useToggle([false, true] as const);
|
||||
const [setHost, setHostList, hostList] = useLocalState((state) => [
|
||||
state.setHost,
|
||||
state.setHostList,
|
||||
@ -48,27 +57,36 @@ export function InstanceOptions({
|
||||
|
||||
return (
|
||||
<Wrapper titleText={t`Select Server`} smallPadding>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<Group gap='xs' justify='space-between' wrap='nowrap'>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={hostKey}
|
||||
onChange={ChangeHost}
|
||||
data={hostListData}
|
||||
disabled={HostListEdit}
|
||||
/>
|
||||
<EditButton
|
||||
setEditing={setHostListEdit}
|
||||
editing={HostListEdit}
|
||||
disabled={HostListEdit}
|
||||
/>
|
||||
<EditButton
|
||||
setEditing={setHostEdit}
|
||||
editing={true}
|
||||
disabled={HostListEdit}
|
||||
saveIcon={<IconCheck />}
|
||||
disabled={hostListEdit}
|
||||
/>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<Tooltip label={t`Edit host options`} position='top'>
|
||||
<ActionButton
|
||||
variant='transparent'
|
||||
disabled={hostListEdit}
|
||||
onClick={setHostListEdit}
|
||||
icon={<IconEdit />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={t`Save host selection`} position='top'>
|
||||
<ActionButton
|
||||
variant='transparent'
|
||||
onClick={setHostEdit}
|
||||
disabled={hostListEdit}
|
||||
icon={<IconCircleCheck />}
|
||||
color='green'
|
||||
/>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{HostListEdit ? (
|
||||
{hostListEdit ? (
|
||||
<>
|
||||
<Divider my={'sm'} />
|
||||
<Text>
|
||||
|
@ -17,11 +17,11 @@ interface ApiImageProps extends ImageProps {
|
||||
* Construct an image container which will load and display the image
|
||||
*/
|
||||
export function ApiImage(props: Readonly<ApiImageProps>) {
|
||||
const { host } = useLocalState.getState();
|
||||
const { getHost } = useLocalState.getState();
|
||||
|
||||
const imageUrl = useMemo(() => {
|
||||
return generateUrl(props.src, host);
|
||||
}, [host, props.src]);
|
||||
return generateUrl(props.src, getHost());
|
||||
}, [getHost, props.src]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
|
@ -65,7 +65,7 @@ export const doBasicLogin = async (
|
||||
password: string,
|
||||
navigate: NavigateFunction
|
||||
) => {
|
||||
const { host } = useLocalState.getState();
|
||||
const { getHost } = useLocalState.getState();
|
||||
const { clearUserState, setAuthenticated, fetchUserState } =
|
||||
useUserState.getState();
|
||||
const { setAuthContext } = useServerApiState.getState();
|
||||
@ -80,6 +80,8 @@ export const doBasicLogin = async (
|
||||
let loginDone = false;
|
||||
let success = false;
|
||||
|
||||
const host: string = getHost();
|
||||
|
||||
// Attempt login with
|
||||
await api
|
||||
.post(
|
||||
@ -156,7 +158,7 @@ export const doLogout = async (navigate: NavigateFunction) => {
|
||||
};
|
||||
|
||||
export const doSimpleLogin = async (email: string) => {
|
||||
const { host } = useLocalState.getState();
|
||||
const { getHost } = useLocalState.getState();
|
||||
const mail = await axios
|
||||
.post(
|
||||
apiUrl(ApiEndpoints.user_simple_login),
|
||||
@ -164,7 +166,7 @@ export const doSimpleLogin = async (email: string) => {
|
||||
email: email
|
||||
},
|
||||
{
|
||||
baseURL: host,
|
||||
baseURL: getHost(),
|
||||
timeout: 2000
|
||||
}
|
||||
)
|
||||
|
@ -36,10 +36,12 @@ export function getDetailUrl(
|
||||
* Returns the edit view URL for a given model type
|
||||
*/
|
||||
export function generateUrl(url: string | URL, base?: string): string {
|
||||
const { host } = useLocalState.getState();
|
||||
const { getHost } = useLocalState.getState();
|
||||
|
||||
let newUrl: string | URL = url;
|
||||
|
||||
const host: string = getHost();
|
||||
|
||||
try {
|
||||
if (base) {
|
||||
newUrl = new URL(url, base).toString();
|
||||
|
@ -22,7 +22,8 @@ declare global {
|
||||
server_list: HostList;
|
||||
default_server: string;
|
||||
show_server_selector: boolean;
|
||||
base_url: string;
|
||||
base_url?: string;
|
||||
api_host?: string;
|
||||
sentry_dsn?: string;
|
||||
environment?: string;
|
||||
};
|
||||
@ -30,12 +31,14 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
// Running in dev mode (i.e. vite)
|
||||
export const IS_DEV = import.meta.env.DEV;
|
||||
export const IS_DEMO = import.meta.env.VITE_DEMO === 'true';
|
||||
export const IS_DEV_OR_DEMO = IS_DEV || IS_DEMO;
|
||||
|
||||
// Filter out any settings that are not defined
|
||||
const loaded_vals = (window.INVENTREE_SETTINGS || {}) as any;
|
||||
|
||||
Object.keys(loaded_vals).forEach((key) => {
|
||||
if (loaded_vals[key] === undefined) {
|
||||
delete loaded_vals[key];
|
||||
@ -48,13 +51,9 @@ Object.keys(loaded_vals).forEach((key) => {
|
||||
|
||||
window.INVENTREE_SETTINGS = {
|
||||
server_list: {
|
||||
'mantine-cqj63coxn': {
|
||||
host: `${window.location.origin}/`,
|
||||
name: 'Current Server'
|
||||
},
|
||||
...(IS_DEV
|
||||
? {
|
||||
'mantine-2j5j5j5j5': {
|
||||
'server-localhost': {
|
||||
host: 'http://localhost:8000',
|
||||
name: 'Localhost'
|
||||
}
|
||||
@ -62,21 +61,25 @@ window.INVENTREE_SETTINGS = {
|
||||
: {}),
|
||||
...(IS_DEV_OR_DEMO
|
||||
? {
|
||||
'mantine-u56l5jt85': {
|
||||
'server-demo': {
|
||||
host: 'https://demo.inventree.org/',
|
||||
name: 'InvenTree Demo'
|
||||
}
|
||||
}
|
||||
: {})
|
||||
: {}),
|
||||
'server-current': {
|
||||
host: `${window.location.origin}/`,
|
||||
name: 'Current Server'
|
||||
}
|
||||
},
|
||||
default_server: IS_DEV
|
||||
? 'mantine-2j5j5j5j5'
|
||||
? 'server-localhost'
|
||||
: IS_DEMO
|
||||
? 'mantine-u56l5jt85'
|
||||
: 'mantine-cqj63coxn',
|
||||
? 'server-demo'
|
||||
: 'server-current',
|
||||
show_server_selector: IS_DEV_OR_DEMO,
|
||||
|
||||
// merge in settings that are already set via django's spa_view or for development
|
||||
// Merge in settings that are already set via django's spa_view or for development
|
||||
...loaded_vals
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,7 @@ interface LocalStateProps {
|
||||
autoupdate: boolean;
|
||||
toggleAutoupdate: () => void;
|
||||
host: string;
|
||||
getHost: () => string;
|
||||
setHost: (newHost: string, newHostKey: string) => void;
|
||||
hostKey: string;
|
||||
hostList: HostList;
|
||||
@ -65,6 +66,28 @@ export const useLocalState = create<LocalStateProps>()(
|
||||
toggleAutoupdate: () =>
|
||||
set((state) => ({ autoupdate: !state.autoupdate })),
|
||||
host: '',
|
||||
getHost: () => {
|
||||
// Retrieve and validate the API host
|
||||
const state = get();
|
||||
|
||||
let host = state.host;
|
||||
|
||||
// If the server provides an override for the host, use that
|
||||
if (window.INVENTREE_SETTINGS?.api_host) {
|
||||
host = window.INVENTREE_SETTINGS.api_host;
|
||||
}
|
||||
|
||||
// If no host is provided, use the first host in the list
|
||||
if (!host && Object.keys(state.hostList).length) {
|
||||
host = Object.values(state.hostList)[0].host;
|
||||
}
|
||||
|
||||
// If no host is provided, fallback to using the current URL (default)
|
||||
if (!host) {
|
||||
host = window.location.origin;
|
||||
}
|
||||
return host;
|
||||
},
|
||||
setHost: (newHost, newHostKey) =>
|
||||
set({ host: newHost, hostKey: newHostKey }),
|
||||
hostKey: '',
|
||||
|
@ -86,8 +86,7 @@ export const navigate = async (page, url: string) => {
|
||||
url = `${baseUrl}/${url}`;
|
||||
}
|
||||
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.goto(url);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -98,7 +97,6 @@ export const loadTab = async (page, tabName) => {
|
||||
.getByLabel(/panel-tabs-/)
|
||||
.getByRole('tab', { name: tabName })
|
||||
.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
};
|
||||
|
||||
// Activate "table" view in certain contexts
|
||||
@ -121,5 +119,4 @@ export const globalSearch = async (page, query) => {
|
||||
await page.getByLabel('global-search-input').clear();
|
||||
await page.getByPlaceholder('Enter search text').fill(query);
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForLoadState('networkidle');
|
||||
};
|
||||
|
@ -1,7 +1,11 @@
|
||||
import type { Browser, Page } from '@playwright/test';
|
||||
import { expect } from './baseFixtures.js';
|
||||
import { baseUrl, logoutUrl, user } from './defaults';
|
||||
import { user } from './defaults';
|
||||
import { navigate } from './helpers.js';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
/*
|
||||
* Perform form based login operation from the "login" URL
|
||||
*/
|
||||
@ -9,7 +13,8 @@ export const doLogin = async (page, username?: string, password?: string) => {
|
||||
username = username ?? user.username;
|
||||
password = password ?? user.password;
|
||||
|
||||
await navigate(page, logoutUrl);
|
||||
await page.goto('http://localhost:8000/web/logout', { waituntil: 'load' });
|
||||
|
||||
await expect(page).toHaveTitle(/^InvenTree.*$/);
|
||||
await page.waitForURL('**/web/login');
|
||||
await page.getByLabel('username').fill(username);
|
||||
@ -19,31 +24,74 @@ export const doLogin = async (page, username?: string, password?: string) => {
|
||||
await page.waitForTimeout(250);
|
||||
};
|
||||
|
||||
export interface CachedLoginOptions {
|
||||
username?: string;
|
||||
password?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// Set of users allowed to do cached login
|
||||
// This is to prevent tests from running with the wrong user
|
||||
const ALLOWED_USERS: string[] = ['admin', 'allaccess', 'reader', 'steven'];
|
||||
|
||||
/*
|
||||
* Perform a quick login based on passing URL parameters
|
||||
*/
|
||||
export const doQuickLogin = async (
|
||||
page,
|
||||
username?: string,
|
||||
password?: string,
|
||||
url?: string
|
||||
) => {
|
||||
username = username ?? user.username;
|
||||
password = password ?? user.password;
|
||||
url = url ?? baseUrl;
|
||||
export const doCachedLogin = async (
|
||||
browser: Browser,
|
||||
options?: CachedLoginOptions
|
||||
): Promise<Page> => {
|
||||
const username = options?.username ?? user.username;
|
||||
const password = options?.password ?? user.password;
|
||||
const url = options?.url ?? '';
|
||||
|
||||
await navigate(page, `${url}/login?login=${username}&password=${password}`);
|
||||
await page.waitForURL('**/web/home');
|
||||
// FAIL if an unsupported username is provided
|
||||
if (!ALLOWED_USERS.includes(username)) {
|
||||
throw new Error(`Invalid username provided to doCachedLogin: ${username}`);
|
||||
}
|
||||
|
||||
// Cache the login state locally - and share between tests
|
||||
const fn = path.resolve(`./playwright/auth/${username}.json`);
|
||||
|
||||
if (fs.existsSync(fn)) {
|
||||
const page = await browser.newPage({
|
||||
storageState: fn
|
||||
});
|
||||
console.log(`Using cached login state for ${username}`);
|
||||
await navigate(page, url);
|
||||
await page.waitForURL('**/web/**');
|
||||
await page.waitForLoadState('load');
|
||||
return page;
|
||||
}
|
||||
|
||||
// Create a new blank page
|
||||
const page = await browser.newPage();
|
||||
|
||||
console.log(`No cache found - logging in for ${username}`);
|
||||
|
||||
// Ensure we start from the login page
|
||||
await page.goto('http://localhost:8000/web/', { waitUntil: 'load' });
|
||||
|
||||
await doLogin(page, username, password);
|
||||
await page.getByLabel('navigation-menu').waitFor({ timeout: 5000 });
|
||||
await page.getByText(/InvenTree Demo Server -/).waitFor();
|
||||
await page.waitForURL('**/web/**');
|
||||
|
||||
// Wait for the dashboard to load
|
||||
await page.getByText('No widgets selected').waitFor();
|
||||
await page.waitForTimeout(250);
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Cache the login state
|
||||
await page.context().storageState({ path: fn });
|
||||
|
||||
if (url) {
|
||||
await navigate(page, url);
|
||||
}
|
||||
|
||||
return page;
|
||||
};
|
||||
|
||||
export const doLogout = async (page) => {
|
||||
await navigate(page, 'logout');
|
||||
await page.goto('http://localhost:8000/web/logout', { waitUntil: 'load' });
|
||||
await page.waitForURL('**/web/login');
|
||||
};
|
||||
|
@ -1,52 +0,0 @@
|
||||
import { test } from './baseFixtures.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
|
||||
test('Modals - Admin', async ({ page }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
|
||||
// use server info
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Server Information About this InvenTree instance'
|
||||
})
|
||||
.click();
|
||||
await page.getByRole('cell', { name: 'Instance Name' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.waitForURL('**/web/home');
|
||||
|
||||
// use license info
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'License Information Licenses for dependencies of the service'
|
||||
})
|
||||
.click();
|
||||
await page.getByText('License Information').first().waitFor();
|
||||
await page.getByRole('tab', { name: 'backend Packages' }).click();
|
||||
await page.getByRole('button', { name: 'Babel BSD License' }).click();
|
||||
await page
|
||||
.getByText('by the Babel Team, see AUTHORS for more information')
|
||||
.waitFor();
|
||||
|
||||
await page.getByRole('tab', { name: 'frontend Packages' }).click();
|
||||
await page.getByRole('button', { name: '@sentry/core MIT' }).click();
|
||||
await page
|
||||
.getByLabel('@sentry/coreMIT')
|
||||
.getByText('Copyright (c) 2019')
|
||||
.waitFor();
|
||||
|
||||
await page
|
||||
.getByLabel('License Information')
|
||||
.getByRole('button')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// use about
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', { name: 'About InvenTree About the InvenTree org' })
|
||||
.click();
|
||||
await page.getByRole('cell', { name: 'InvenTree Version' }).click();
|
||||
});
|
@ -8,13 +8,15 @@ import {
|
||||
navigate,
|
||||
setTableChoiceFilter
|
||||
} from '../helpers.ts';
|
||||
import { doQuickLogin } from '../login.ts';
|
||||
import { doCachedLogin } from '../login.ts';
|
||||
|
||||
test('Build Order - Basic Tests', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Basic Tests', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Navigate to the correct build order
|
||||
await page.getByRole('tab', { name: 'Manufacturing', exact: true }).click();
|
||||
await page.getByRole('tab', { name: 'Manufacturing' }).click();
|
||||
await page.waitForURL('**/manufacturing/index/**');
|
||||
|
||||
await loadTab(page, 'Build Orders');
|
||||
|
||||
await clearTableFilters(page);
|
||||
@ -91,8 +93,8 @@ test('Build Order - Basic Tests', async ({ page }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Calendar', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Calendar', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/index/buildorders');
|
||||
await activateCalendarView(page);
|
||||
@ -114,8 +116,8 @@ test('Build Order - Calendar', async ({ page }) => {
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
test('Build Order - Edit', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Edit', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/build-order/22/');
|
||||
|
||||
@ -141,8 +143,8 @@ test('Build Order - Edit', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
test('Build Order - Build Outputs', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Build Outputs', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/index/');
|
||||
await loadTab(page, 'Build Orders');
|
||||
@ -217,8 +219,8 @@ test('Build Order - Build Outputs', async ({ page }) => {
|
||||
await page.getByText('Build outputs have been completed').waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Allocation', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Allocation', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/build-order/1/line-items');
|
||||
|
||||
@ -317,8 +319,8 @@ test('Build Order - Allocation', async ({ page }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Filters', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Filters', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/index/buildorders');
|
||||
|
||||
@ -351,8 +353,8 @@ test('Build Order - Filters', async ({ page }) => {
|
||||
await page.getByText('Pending Approval').first().waitFor();
|
||||
});
|
||||
|
||||
test('Build Order - Duplicate', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Build Order - Duplicate', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'manufacturing/build-order/24/details');
|
||||
await page.getByLabel('action-menu-build-order-').click();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { test } from '../baseFixtures.js';
|
||||
import { loadTab, navigate } from '../helpers.js';
|
||||
import { doQuickLogin } from '../login.js';
|
||||
import { doCachedLogin } from '../login.js';
|
||||
|
||||
test('Company', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Company', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await navigate(page, 'company/1/details');
|
||||
await page.getByLabel('Details').getByText('DigiKey Electronics').waitFor();
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { test } from '../baseFixtures.js';
|
||||
import { loadTab, navigate } from '../helpers.js';
|
||||
import { doQuickLogin } from '../login.js';
|
||||
import { doCachedLogin } from '../login.js';
|
||||
|
||||
test('Core User/Group/Contact', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Core User/Group/Contact', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// groups
|
||||
await navigate(page, '/core');
|
||||
await page.getByText('System Overview', { exact: true }).click();
|
||||
await page.getByText('System Overview', { exact: true }).first().click();
|
||||
await loadTab(page, 'Groups');
|
||||
await page.getByRole('cell', { name: 'all access' }).click();
|
||||
await page.getByText('Group: all access', { exact: true }).click();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { test } from '../baseFixtures.js';
|
||||
import { doQuickLogin } from '../login.js';
|
||||
import { doCachedLogin } from '../login.js';
|
||||
import { setPluginState } from '../settings.js';
|
||||
|
||||
test('Dashboard - Basic', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Dashboard - Basic', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByText('Use the menu to add widgets').waitFor();
|
||||
|
||||
@ -35,7 +35,7 @@ test('Dashboard - Basic', async ({ page }) => {
|
||||
await page.getByLabel('dashboard-accept-layout').click();
|
||||
});
|
||||
|
||||
test('Dashboard - Plugins', async ({ page, request }) => {
|
||||
test('Dashboard - Plugins', async ({ browser, request }) => {
|
||||
// Ensure that the "SampleUI" plugin is enabled
|
||||
await setPluginState({
|
||||
request,
|
||||
@ -43,7 +43,7 @@ test('Dashboard - Plugins', async ({ page, request }) => {
|
||||
state: true
|
||||
});
|
||||
|
||||
await doQuickLogin(page);
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Add a dashboard widget from the SampleUI plugin
|
||||
await page.getByLabel('dashboard-menu').click();
|
||||
|
@ -5,15 +5,17 @@ import {
|
||||
loadTab,
|
||||
navigate
|
||||
} from '../helpers';
|
||||
import { doQuickLogin } from '../login';
|
||||
import { doCachedLogin } from '../login';
|
||||
|
||||
/**
|
||||
* CHeck each panel tab for the "Parts" page
|
||||
*/
|
||||
test('Parts - Tabs', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Parts - Tabs', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Parts' }).click();
|
||||
await page.waitForURL('**/part/category/index/**');
|
||||
|
||||
await page
|
||||
.getByLabel('panel-tabs-partcategory')
|
||||
.getByRole('tab', { name: 'Parts' })
|
||||
@ -56,10 +58,8 @@ test('Parts - Tabs', async ({ page }) => {
|
||||
await loadTab(page, 'Build Orders');
|
||||
});
|
||||
|
||||
test('Parts - Manufacturer Parts', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/84/suppliers');
|
||||
test('Parts - Manufacturer Parts', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/84/suppliers' });
|
||||
|
||||
await loadTab(page, 'Suppliers');
|
||||
await page.getByText('Hammond Manufacturing').click();
|
||||
@ -69,10 +69,8 @@ test('Parts - Manufacturer Parts', async ({ page }) => {
|
||||
await page.getByText('1551ACLR - 1551ACLR').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Supplier Parts', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/15/suppliers');
|
||||
test('Parts - Supplier Parts', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/15/suppliers' });
|
||||
|
||||
await loadTab(page, 'Suppliers');
|
||||
await page.getByRole('cell', { name: 'DIG-84670-SJI' }).click();
|
||||
@ -82,11 +80,8 @@ test('Parts - Supplier Parts', async ({ page }) => {
|
||||
await page.getByText('DIG-84670-SJI - R_550R_0805_1%').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Locking', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
// Navigate to a known assembly which is *not* locked
|
||||
await navigate(page, 'part/104/bom');
|
||||
test('Parts - Locking', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/104/bom' });
|
||||
await loadTab(page, 'Bill of Materials');
|
||||
await page.getByLabel('action-button-add-bom-item').waitFor();
|
||||
await loadTab(page, 'Parameters');
|
||||
@ -108,11 +103,9 @@ test('Parts - Locking', async ({ page }) => {
|
||||
await page.getByText('Part parameters cannot be').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Allocations', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
test('Parts - Allocations', async ({ browser }) => {
|
||||
// Let's look at the allocations for a single stock item
|
||||
await navigate(page, 'stock/item/324/');
|
||||
const page = await doCachedLogin(browser, { url: 'stock/item/324/' });
|
||||
await loadTab(page, 'Allocations');
|
||||
|
||||
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
|
||||
@ -173,11 +166,9 @@ test('Parts - Allocations', async ({ page }) => {
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
test('Parts - Pricing (Nothing, BOM)', async ({ browser }) => {
|
||||
// Part with no history
|
||||
await navigate(page, 'part/82/pricing');
|
||||
const page = await doCachedLogin(browser, { url: 'part/82/pricing' });
|
||||
|
||||
await page.getByText('Small plastic enclosure, black').waitFor();
|
||||
await loadTab(page, 'Part Pricing');
|
||||
@ -223,11 +214,9 @@ test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {
|
||||
await page.waitForURL('**/part/98/**');
|
||||
});
|
||||
|
||||
test('Parts - Pricing (Supplier)', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Parts - Pricing (Supplier)', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/55/pricing' });
|
||||
|
||||
// Part
|
||||
await navigate(page, 'part/55/pricing');
|
||||
await page.getByText('Ceramic capacitor, 100nF in').waitFor();
|
||||
await loadTab(page, 'Part Pricing');
|
||||
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
||||
@ -249,11 +238,8 @@ test('Parts - Pricing (Supplier)', async ({ page }) => {
|
||||
// await page.waitForURL('**/purchasing/supplier-part/697/');
|
||||
});
|
||||
|
||||
test('Parts - Pricing (Variant)', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
// Part
|
||||
await navigate(page, 'part/106/pricing');
|
||||
test('Parts - Pricing (Variant)', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/106/pricing' });
|
||||
await page.getByText('A chair - available in multiple colors').waitFor();
|
||||
await loadTab(page, 'Part Pricing');
|
||||
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
||||
@ -275,11 +261,8 @@ test('Parts - Pricing (Variant)', async ({ page }) => {
|
||||
await page.waitForURL('**/part/109/**');
|
||||
});
|
||||
|
||||
test('Parts - Pricing (Internal)', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
// Part
|
||||
await navigate(page, 'part/65/pricing');
|
||||
test('Parts - Pricing (Internal)', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/65/pricing' });
|
||||
await page.getByText('Socket head cap screw, M2').waitFor();
|
||||
await loadTab(page, 'Part Pricing');
|
||||
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
||||
@ -300,11 +283,9 @@ test('Parts - Pricing (Internal)', async ({ page }) => {
|
||||
await page.getByText('Part *M2x4 SHCSSocket head').click();
|
||||
});
|
||||
|
||||
test('Parts - Pricing (Purchase)', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Parts - Pricing (Purchase)', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/69/pricing' });
|
||||
|
||||
// Part
|
||||
await navigate(page, 'part/69/pricing');
|
||||
await page.getByText('1.25mm Pitch, PicoBlade PCB').waitFor();
|
||||
await loadTab(page, 'Part Pricing');
|
||||
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
||||
@ -322,10 +303,8 @@ test('Parts - Pricing (Purchase)', async ({ page }) => {
|
||||
await page.getByText('2022-04-29').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Attachments', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/69/attachments');
|
||||
test('Parts - Attachments', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/69/attachments' });
|
||||
|
||||
// Submit a new external link
|
||||
await page.getByLabel('action-button-add-external-').click();
|
||||
@ -344,10 +323,8 @@ test('Parts - Attachments', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
test('Parts - Parameters', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/69/parameters');
|
||||
test('Parts - Parameters', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/69/parameters' });
|
||||
|
||||
// Create a new template
|
||||
await page.getByLabel('action-button-add-parameter').click();
|
||||
@ -371,10 +348,8 @@ test('Parts - Parameters', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
test('Parts - Notes', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/69/notes');
|
||||
test('Parts - Notes', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/69/notes' });
|
||||
|
||||
// Enable editing
|
||||
await page.getByLabel('Enable Editing').waitFor();
|
||||
@ -393,20 +368,16 @@ test('Parts - Notes', async ({ page }) => {
|
||||
await page.getByLabel('Close Editor').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - 404', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/99999/');
|
||||
test('Parts - 404', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/99999/' });
|
||||
await page.getByText('Page Not Found', { exact: true }).waitFor();
|
||||
|
||||
// Clear out any console error messages
|
||||
await page.evaluate(() => console.clear());
|
||||
});
|
||||
|
||||
test('Parts - Revision', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/906/details');
|
||||
test('Parts - Revision', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/906/details' });
|
||||
|
||||
await page.getByText('Revision of').waitFor();
|
||||
await page.getByText('Select Part Revision').waitFor();
|
||||
@ -421,10 +392,10 @@ test('Parts - Revision', async ({ page }) => {
|
||||
await page.getByText('Select Part Revision').waitFor();
|
||||
});
|
||||
|
||||
test('Parts - Bulk Edit', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'part/category/index/parts');
|
||||
test('Parts - Bulk Edit', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'part/category/index/parts'
|
||||
});
|
||||
|
||||
// Edit the category for multiple parts
|
||||
await page.getByLabel('Select record 1', { exact: true }).click();
|
||||
|
@ -11,12 +11,14 @@ import {
|
||||
openFilterDrawer,
|
||||
setTableChoiceFilter
|
||||
} from '../helpers.ts';
|
||||
import { doQuickLogin } from '../login.ts';
|
||||
import { doCachedLogin } from '../login.ts';
|
||||
|
||||
test('Purchase Orders - Table', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Table', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
|
||||
await loadTab(page, 'Purchase Orders');
|
||||
await activateTableView(page);
|
||||
|
||||
@ -42,10 +44,11 @@ test('Purchase Orders - Table', async ({ page }) => {
|
||||
await page.getByText('2025-07-17').waitFor(); // Target Date
|
||||
});
|
||||
|
||||
test('Purchase Orders - Calendar', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Calendar', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
await loadTab(page, 'Purchase Orders');
|
||||
|
||||
// Ensure view is in "calendar" mode
|
||||
@ -66,10 +69,11 @@ test('Purchase Orders - Calendar', async ({ page }) => {
|
||||
await activateTableView(page);
|
||||
});
|
||||
|
||||
test('Purchase Orders - Barcodes', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Barcodes', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'purchasing/purchase-order/13/detail'
|
||||
});
|
||||
|
||||
await navigate(page, 'purchasing/purchase-order/13/detail');
|
||||
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
|
||||
|
||||
// Display QR code
|
||||
@ -126,10 +130,11 @@ test('Purchase Orders - Barcodes', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
|
||||
});
|
||||
|
||||
test('Purchase Orders - General', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - General', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
|
||||
await page.getByRole('cell', { name: 'PO0012' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
@ -179,10 +184,15 @@ test('Purchase Orders - General', async ({ page }) => {
|
||||
await page.getByRole('tab', { name: 'Details' }).waitFor();
|
||||
});
|
||||
|
||||
test('Purchase Orders - Filters', async ({ page }) => {
|
||||
await doQuickLogin(page, 'reader', 'readonly');
|
||||
test('Purchase Orders - Filters', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'reader',
|
||||
password: 'readonly'
|
||||
});
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
|
||||
await loadTab(page, 'Purchase Orders');
|
||||
await activateTableView(page);
|
||||
|
||||
@ -204,11 +214,13 @@ test('Purchase Orders - Filters', async ({ page }) => {
|
||||
await page.getByRole('option', { name: 'Target Date After' }).waitFor();
|
||||
});
|
||||
|
||||
test('Purchase Orders - Order Parts', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Order Parts', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Open "Order Parts" wizard from the "parts" table
|
||||
await page.getByRole('tab', { name: 'Parts' }).click();
|
||||
await page.waitForURL('**/part/category/index/**');
|
||||
|
||||
await page
|
||||
.getByLabel('panel-tabs-partcategory')
|
||||
.getByRole('tab', { name: 'Parts' })
|
||||
@ -284,10 +296,12 @@ test('Purchase Orders - Order Parts', async ({ page }) => {
|
||||
/**
|
||||
* Tests for receiving items against a purchase order
|
||||
*/
|
||||
test('Purchase Orders - Receive Items', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Receive Items', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Purchasing' }).click();
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
|
||||
await page.getByRole('cell', { name: 'PO0014' }).click();
|
||||
|
||||
await loadTab(page, 'Order Details');
|
||||
@ -351,10 +365,11 @@ test('Purchase Orders - Receive Items', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'bucket' }).first().waitFor();
|
||||
});
|
||||
|
||||
test('Purchase Orders - Duplicate', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Purchase Orders - Duplicate', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'purchasing/purchase-order/13/detail'
|
||||
});
|
||||
|
||||
await navigate(page, 'purchasing/purchase-order/13/detail');
|
||||
await page.getByLabel('action-menu-order-actions').click();
|
||||
await page.getByLabel('action-menu-order-actions-duplicate').click();
|
||||
|
||||
|
@ -4,15 +4,13 @@ import {
|
||||
clearTableFilters,
|
||||
globalSearch,
|
||||
loadTab,
|
||||
navigate,
|
||||
setTableChoiceFilter
|
||||
} from '../helpers.ts';
|
||||
import { doQuickLogin } from '../login.ts';
|
||||
import { doCachedLogin } from '../login.ts';
|
||||
|
||||
test('Sales Orders - Tabs', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Sales Orders - Tabs', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'sales/index/' });
|
||||
|
||||
await navigate(page, 'sales/index/');
|
||||
await page.waitForURL('**/web/sales/**');
|
||||
|
||||
await loadTab(page, 'Sales Orders');
|
||||
@ -63,10 +61,12 @@ test('Sales Orders - Tabs', async ({ page }) => {
|
||||
await loadTab(page, 'Notes');
|
||||
});
|
||||
|
||||
test('Sales Orders - Basic Tests', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Sales Orders - Basic Tests', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Sales' }).click();
|
||||
await page.waitForURL('**/sales/index/**');
|
||||
|
||||
await loadTab(page, 'Sales Orders');
|
||||
|
||||
await clearTableFilters(page);
|
||||
@ -102,10 +102,12 @@ test('Sales Orders - Basic Tests', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
|
||||
});
|
||||
|
||||
test('Sales Orders - Shipments', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Sales Orders - Shipments', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('tab', { name: 'Sales' }).click();
|
||||
await page.waitForURL('**/sales/index/**');
|
||||
|
||||
await loadTab(page, 'Sales Orders');
|
||||
|
||||
await clearTableFilters(page);
|
||||
@ -131,7 +133,7 @@ test('Sales Orders - Shipments', async ({ page }) => {
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
|
||||
// Ensure the form has loaded
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
let tracking_number = await page
|
||||
.getByLabel('text-field-tracking_number')
|
||||
@ -201,10 +203,11 @@ test('Sales Orders - Shipments', async ({ page }) => {
|
||||
.click();
|
||||
});
|
||||
|
||||
test('Sales Orders - Duplicate', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Sales Orders - Duplicate', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'sales/sales-order/11/detail'
|
||||
});
|
||||
|
||||
await navigate(page, 'sales/sales-order/11/detail');
|
||||
await page.getByLabel('action-menu-order-actions').click();
|
||||
await page.getByLabel('action-menu-order-actions-duplicate').click();
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { test } from '../baseFixtures';
|
||||
import { navigate } from '../helpers';
|
||||
import { doQuickLogin } from '../login';
|
||||
import { doCachedLogin } from '../login';
|
||||
|
||||
const scan = async (page, barcode) => {
|
||||
await page.getByLabel('barcode-input-scanner').click();
|
||||
@ -8,8 +7,8 @@ const scan = async (page, barcode) => {
|
||||
await page.getByRole('button', { name: 'Scan', exact: true }).click();
|
||||
};
|
||||
|
||||
test('Scanning - Dialog', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Scanning - Dialog', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.getByRole('button', { name: 'Open Barcode Scanner' }).click();
|
||||
await scan(page, '{"part": 15}');
|
||||
@ -19,8 +18,8 @@ test('Scanning - Dialog', async ({ page }) => {
|
||||
await page.getByText('Required:').waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - Basic', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Scanning - Basic', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Navigate to the 'scan' page
|
||||
await page.getByLabel('navigation-menu').click();
|
||||
@ -40,9 +39,8 @@ test('Scanning - Basic', async ({ page }) => {
|
||||
await page.getByText('No match found for barcode').waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - Part', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - Part', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
|
||||
await scan(page, '{"part": 1}');
|
||||
|
||||
@ -51,9 +49,8 @@ test('Scanning - Part', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'part', exact: true }).waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - Stockitem', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - Stockitem', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
await scan(page, '{"stockitem": 408}');
|
||||
|
||||
await page.getByText('1551ABK').waitFor();
|
||||
@ -61,9 +58,9 @@ test('Scanning - Stockitem', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'Quantity: 100' }).waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - StockLocation', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - StockLocation', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
|
||||
await scan(page, '{"stocklocation": 3}');
|
||||
|
||||
// stocklocation: 3
|
||||
@ -74,20 +71,17 @@ test('Scanning - StockLocation', async ({ page }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - SupplierPart', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - SupplierPart', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
await scan(page, '{"supplierpart": 204}');
|
||||
|
||||
// supplierpart: 204
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByText('1551ABK').first().waitFor();
|
||||
await page.getByRole('cell', { name: 'supplierpart', exact: true }).waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - PurchaseOrder', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - PurchaseOrder', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
await scan(page, '{"purchaseorder": 12}');
|
||||
|
||||
// purchaseorder: 12
|
||||
@ -98,9 +92,9 @@ test('Scanning - PurchaseOrder', async ({ page }) => {
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - SalesOrder', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - SalesOrder', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
|
||||
await scan(page, '{"salesorder": 6}');
|
||||
|
||||
// salesorder: 6
|
||||
@ -109,9 +103,8 @@ test('Scanning - SalesOrder', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'salesorder', exact: true }).waitFor();
|
||||
});
|
||||
|
||||
test('Scanning - Build', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await navigate(page, 'scan/');
|
||||
test('Scanning - Build', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'scan/' });
|
||||
await scan(page, '{"build": 8}');
|
||||
|
||||
// build: 8
|
||||
|
@ -7,12 +7,11 @@ import {
|
||||
openFilterDrawer,
|
||||
setTableChoiceFilter
|
||||
} from '../helpers.js';
|
||||
import { doQuickLogin } from '../login.js';
|
||||
import { doCachedLogin } from '../login.js';
|
||||
|
||||
test('Stock - Basic Tests', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Stock - Basic Tests', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/location/index/' });
|
||||
|
||||
await navigate(page, 'stock/location/index/');
|
||||
await page.waitForURL('**/web/stock/location/**');
|
||||
|
||||
await loadTab(page, 'Location Details');
|
||||
@ -39,10 +38,9 @@ test('Stock - Basic Tests', async ({ page }) => {
|
||||
await loadTab(page, 'Installed Items');
|
||||
});
|
||||
|
||||
test('Stock - Location Tree', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Stock - Location Tree', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/location/index/' });
|
||||
|
||||
await navigate(page, 'stock/location/index/');
|
||||
await page.waitForURL('**/web/stock/location/**');
|
||||
await loadTab(page, 'Location Details');
|
||||
|
||||
@ -56,10 +54,13 @@ test('Stock - Location Tree', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'Factory' }).first().waitFor();
|
||||
});
|
||||
|
||||
test('Stock - Filters', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
test('Stock - Filters', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff',
|
||||
url: '/stock/location/index/'
|
||||
});
|
||||
|
||||
await navigate(page, 'stock/location/index/');
|
||||
await loadTab(page, 'Stock Items');
|
||||
|
||||
await openFilterDrawer(page);
|
||||
@ -101,8 +102,8 @@ test('Stock - Filters', async ({ page }) => {
|
||||
await clearTableFilters(page);
|
||||
});
|
||||
|
||||
test('Stock - Serial Numbers', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Stock - Serial Numbers', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
// Use the "global search" functionality to find a part we are interested in
|
||||
// This is to exercise the search functionality and ensure it is working as expected
|
||||
@ -167,10 +168,8 @@ test('Stock - Serial Numbers', async ({ page }) => {
|
||||
/**
|
||||
* Test various 'actions' on the stock detail page
|
||||
*/
|
||||
test('Stock - Stock Actions', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
await navigate(page, 'stock/item/1225/details');
|
||||
test('Stock - Stock Actions', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/item/1225/details' });
|
||||
|
||||
// Helper function to launch a stock action
|
||||
const launchStockAction = async (action: string) => {
|
||||
@ -231,11 +230,9 @@ test('Stock - Stock Actions', async ({ page }) => {
|
||||
await page.getByLabel('action-menu-stock-operations-return').click();
|
||||
});
|
||||
|
||||
test('Stock - Tracking', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Stock - Tracking', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/item/176/details' });
|
||||
|
||||
// Navigate to the "stock item" page
|
||||
await navigate(page, 'stock/item/176/details/');
|
||||
await page.getByRole('link', { name: 'Widget Assembly # 2' }).waitFor();
|
||||
|
||||
// Navigate to the "stock tracking" tab
|
||||
|
@ -1,6 +1,6 @@
|
||||
import test from '@playwright/test';
|
||||
import { globalSearch, loadTab, navigate } from './helpers';
|
||||
import { doQuickLogin } from './login';
|
||||
import { doCachedLogin } from './login';
|
||||
|
||||
// Helper function to open the export data dialog
|
||||
const openExportDialog = async (page) => {
|
||||
@ -11,11 +11,12 @@ const openExportDialog = async (page) => {
|
||||
};
|
||||
|
||||
// Test data export for various order types
|
||||
test('Exporting - Orders', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
|
||||
// Download list of purchase orders
|
||||
await navigate(page, 'purchasing/index/purchase-orders');
|
||||
test('Exporting - Orders', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff',
|
||||
url: 'purchasing/index/purchase-orders'
|
||||
});
|
||||
|
||||
await openExportDialog(page);
|
||||
|
||||
@ -69,8 +70,11 @@ test('Exporting - Orders', async ({ page }) => {
|
||||
});
|
||||
|
||||
// Test for custom BOM exporter
|
||||
test('Exporting - BOM', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
test('Exporting - BOM', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff'
|
||||
});
|
||||
|
||||
await globalSearch(page, 'MAST');
|
||||
await page.getByLabel('search-group-results-part').locator('a').click();
|
||||
|
@ -1,11 +1,15 @@
|
||||
/** Unit tests for form validation, rendering, etc */
|
||||
import test from 'playwright/test';
|
||||
import { navigate } from './helpers';
|
||||
import { doQuickLogin } from './login';
|
||||
import { doCachedLogin } from './login';
|
||||
|
||||
test('Forms - Stock Item Validation', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff',
|
||||
url: 'stock/location/index/stock-items'
|
||||
});
|
||||
|
||||
test('Forms - Stock Item Validation', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
await navigate(page, 'stock/location/index/stock-items');
|
||||
await page.waitForURL('**/web/stock/location/**');
|
||||
|
||||
// Create new stock item form
|
||||
@ -74,9 +78,12 @@ test('Forms - Stock Item Validation', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'Electronics Lab' }).waitFor();
|
||||
});
|
||||
|
||||
test('Forms - Supplier Validation', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
await navigate(page, 'purchasing/index/suppliers');
|
||||
test('Forms - Supplier Validation', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff',
|
||||
url: 'purchasing/index/suppliers'
|
||||
});
|
||||
await page.waitForURL('**/purchasing/index/**');
|
||||
|
||||
await page.getByLabel('action-button-add-company').click();
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { test } from './baseFixtures.js';
|
||||
import { globalSearch, navigate } from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
import { globalSearch } from './helpers.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
|
||||
/**
|
||||
* Test for integration of django admin button
|
||||
*/
|
||||
test('Admin Button', async ({ page }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
await navigate(page, 'company/1/details');
|
||||
test('Admin Button', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree',
|
||||
url: 'company/1/details'
|
||||
});
|
||||
|
||||
// Click on the admin button
|
||||
await page.getByLabel(/action-button-open-in-admin/).click();
|
||||
@ -18,8 +21,11 @@ test('Admin Button', async ({ page }) => {
|
||||
});
|
||||
|
||||
// Tests for the global search functionality
|
||||
test('Search', async ({ page }) => {
|
||||
await doQuickLogin(page, 'steven', 'wizardstaff');
|
||||
test('Search', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'steven',
|
||||
password: 'wizardstaff'
|
||||
});
|
||||
|
||||
await globalSearch(page, 'another customer');
|
||||
|
||||
|
@ -1,41 +1,7 @@
|
||||
import { expect, test } from './baseFixtures.js';
|
||||
import { logoutUrl, user } from './defaults.js';
|
||||
import { logoutUrl } from './defaults.js';
|
||||
import { navigate } from './helpers.js';
|
||||
import { doLogin, doQuickLogin } from './login.js';
|
||||
|
||||
test('Login - Basic Test', async ({ page }) => {
|
||||
await doLogin(page);
|
||||
|
||||
// Check that the username is provided
|
||||
await page.getByText(user.username);
|
||||
|
||||
// Logout (via menu)
|
||||
await page.getByRole('button', { name: 'Ally Access' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Logout' }).click();
|
||||
|
||||
await page.waitForURL('**/web/login');
|
||||
await page.getByLabel('username');
|
||||
});
|
||||
|
||||
test('Login - Quick Test', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
// Check that the username is provided
|
||||
await page.getByText(user.username);
|
||||
|
||||
await expect(page).toHaveTitle(/^InvenTree/);
|
||||
|
||||
// Go to the dashboard
|
||||
await navigate(page, '');
|
||||
await page.waitForURL('**/web');
|
||||
|
||||
await page.getByText('InvenTree Demo Server - ').waitFor();
|
||||
|
||||
// Logout (via URL)
|
||||
await navigate(page, 'logout');
|
||||
await page.waitForURL('**/web/login');
|
||||
await page.getByLabel('username');
|
||||
});
|
||||
import { doLogin } from './login.js';
|
||||
|
||||
/**
|
||||
* Test various types of login failure
|
||||
@ -79,7 +45,7 @@ test('Login - Failures', async ({ page }) => {
|
||||
});
|
||||
|
||||
test('Login - Change Password', async ({ page }) => {
|
||||
await doQuickLogin(page, 'noaccess', 'youshallnotpass');
|
||||
await doLogin(page, 'noaccess', 'youshallnotpass');
|
||||
|
||||
// Navigate to the 'change password' page
|
||||
await navigate(page, 'settings/user/account');
|
||||
@ -105,6 +71,4 @@ test('Login - Change Password', async ({ page }) => {
|
||||
|
||||
await page.getByText('Password Changed').waitFor();
|
||||
await page.getByText('The password was set successfully').waitFor();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
@ -1,8 +1,61 @@
|
||||
import { systemKey, test } from './baseFixtures.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
|
||||
test('Quick Command', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Modals - Admin', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// use server info
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Server Information About this InvenTree instance'
|
||||
})
|
||||
.click();
|
||||
await page.getByRole('cell', { name: 'Instance Name' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// use license info
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'License Information Licenses for dependencies of the service'
|
||||
})
|
||||
.click();
|
||||
await page.getByText('License Information').first().waitFor();
|
||||
await page.getByRole('tab', { name: 'backend Packages' }).click();
|
||||
await page.getByRole('button', { name: 'Babel BSD License' }).click();
|
||||
await page
|
||||
.getByText('by the Babel Team, see AUTHORS for more information')
|
||||
.waitFor();
|
||||
|
||||
await page.getByRole('tab', { name: 'frontend Packages' }).click();
|
||||
await page.getByRole('button', { name: '@sentry/core MIT' }).click();
|
||||
await page
|
||||
.getByLabel('@sentry/coreMIT')
|
||||
.getByText('Copyright (c) 2019')
|
||||
.waitFor();
|
||||
|
||||
await page
|
||||
.getByLabel('License Information')
|
||||
.getByRole('button')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// use about
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', { name: 'About InvenTree About the InvenTree org' })
|
||||
.click();
|
||||
await page.getByRole('cell', { name: 'InvenTree Version' }).click();
|
||||
});
|
||||
|
||||
test('Quick Command', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Open Spotlight with Keyboard Shortcut and Search
|
||||
await page.locator('body').press(`${systemKey}+k`);
|
||||
@ -10,11 +63,12 @@ test('Quick Command', async ({ page }) => {
|
||||
await page.getByPlaceholder('Search...').fill('Dashboard');
|
||||
await page.getByPlaceholder('Search...').press('Tab');
|
||||
await page.getByPlaceholder('Search...').press('Enter');
|
||||
await page.waitForURL('**/web/home');
|
||||
});
|
||||
|
||||
test('Quick Command - No Keys', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Quick Command - No Keys', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Open Spotlight with Button
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
@ -23,15 +77,15 @@ test('Quick Command - No Keys', async ({ page }) => {
|
||||
.click();
|
||||
|
||||
await page.getByText('InvenTree Demo Server - ').waitFor();
|
||||
await page.waitForURL('**/web/home');
|
||||
|
||||
// Use navigation menu
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
||||
.getByRole('button', { name: 'Open Navigation Open the main' })
|
||||
.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('button', { name: 'Open Navigation' }).click();
|
||||
|
||||
await page.waitForTimeout(250);
|
||||
|
||||
// assert the nav headers are visible
|
||||
await page.getByText('Navigation').waitFor();
|
||||
@ -55,8 +109,6 @@ test('Quick Command - No Keys', async ({ page }) => {
|
||||
await page.getByRole('cell', { name: 'Instance Name' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.waitForURL('**/web/home');
|
||||
|
||||
// use license info
|
||||
await page.getByLabel('open-spotlight').click();
|
||||
await page
|
@ -6,12 +6,15 @@ import {
|
||||
loadTab,
|
||||
navigate
|
||||
} from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
import { setPluginState, setSettingState } from './settings.js';
|
||||
|
||||
// Unit test for plugin settings
|
||||
test('Plugins - Settings', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
test('Plugins - Settings', async ({ browser, request }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// Ensure that the SampleIntegration plugin is enabled
|
||||
await setPluginState({
|
||||
@ -57,11 +60,14 @@ test('Plugins - Settings', async ({ page, request }) => {
|
||||
});
|
||||
|
||||
// Test base plugin functionality
|
||||
test('Plugins - Functionality', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
|
||||
test('Plugins - Functionality', async ({ browser }) => {
|
||||
// Navigate and select the plugin
|
||||
await navigate(page, 'settings/admin/plugin/');
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree',
|
||||
url: 'settings/admin/plugin/'
|
||||
});
|
||||
|
||||
await clearTableFilters(page);
|
||||
await page.getByPlaceholder('Search').fill('sample');
|
||||
await page.waitForLoadState('networkidle');
|
||||
@ -80,8 +86,11 @@ test('Plugins - Functionality', async ({ page, request }) => {
|
||||
await page.getByText('The plugin was deactivated').waitFor();
|
||||
});
|
||||
|
||||
test('Plugins - Panels', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
test('Plugins - Panels', async ({ browser, request }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// Ensure that UI plugins are enabled
|
||||
await setSettingState({
|
||||
@ -108,7 +117,7 @@ test('Plugins - Panels', async ({ page, request }) => {
|
||||
await loadTab(page, 'Part Details');
|
||||
|
||||
// Allow time for the plugin panels to load (they are loaded asynchronously)
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check out each of the plugin panels
|
||||
await loadTab(page, 'Broken Panel');
|
||||
@ -139,8 +148,11 @@ test('Plugins - Panels', async ({ page, request }) => {
|
||||
/**
|
||||
* Unit test for custom admin integration for plugins
|
||||
*/
|
||||
test('Plugins - Custom Admin', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
test('Plugins - Custom Admin', async ({ browser, request }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// Ensure that the SampleUI plugin is enabled
|
||||
await setPluginState({
|
||||
@ -170,8 +182,11 @@ test('Plugins - Custom Admin', async ({ page, request }) => {
|
||||
await page.getByText('hello: world').waitFor();
|
||||
});
|
||||
|
||||
test('Plugins - Locate Item', async ({ page, request }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
test('Plugins - Locate Item', async ({ browser, request }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// Ensure that the sample location plugin is enabled
|
||||
await setPluginState({
|
||||
@ -184,6 +199,7 @@ test('Plugins - Locate Item', async ({ page, request }) => {
|
||||
|
||||
// Navigate to the "stock item" page
|
||||
await navigate(page, 'stock/item/287/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// "Locate" this item
|
||||
await page.getByLabel('action-button-locate-item').click();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { expect, test } from './baseFixtures.js';
|
||||
import { activateTableView, loadTab, navigate } from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
import { activateTableView, loadTab } from './helpers.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
import { setPluginState } from './settings.js';
|
||||
|
||||
/*
|
||||
@ -8,10 +8,9 @@ import { setPluginState } from './settings.js';
|
||||
* Select a number of stock items from the table,
|
||||
* and print labels against them
|
||||
*/
|
||||
test('Label Printing', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Label Printing', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/location/index/' });
|
||||
|
||||
await navigate(page, 'stock/location/index/');
|
||||
await page.waitForURL('**/web/stock/location/**');
|
||||
|
||||
await loadTab(page, 'Stock Items');
|
||||
@ -50,10 +49,9 @@ test('Label Printing', async ({ page }) => {
|
||||
* Navigate to a PurchaseOrder detail page,
|
||||
* and print a report against it.
|
||||
*/
|
||||
test('Report Printing', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
test('Report Printing', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'stock/location/index/' });
|
||||
|
||||
await navigate(page, 'stock/location/index/');
|
||||
await page.waitForURL('**/web/stock/location/**');
|
||||
|
||||
// Navigate to a specific PurchaseOrder
|
||||
@ -81,9 +79,11 @@ test('Report Printing', async ({ page }) => {
|
||||
await page.context().close();
|
||||
});
|
||||
|
||||
test('Report Editing', async ({ page, request }) => {
|
||||
const [username, password] = ['admin', 'inventree'];
|
||||
await doQuickLogin(page, username, password);
|
||||
test('Report Editing', async ({ browser, request }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// activate the sample plugin for this test
|
||||
await setPluginState({
|
||||
|
@ -1,49 +1,45 @@
|
||||
import { expect, test } from './baseFixtures.js';
|
||||
import { apiUrl } from './defaults.js';
|
||||
import { getRowFromCell, loadTab, navigate } from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
import { doCachedLogin } from './login.js';
|
||||
import { setSettingState } from './settings.js';
|
||||
|
||||
/**
|
||||
* Adjust language and color settings
|
||||
*
|
||||
* TODO: Reimplement this - without logging out a cached user
|
||||
*/
|
||||
test('Settings - Language / Color', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
// 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: '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('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);
|
||||
|
||||
// Note: changes to the dashboard have invalidated these tests (for now)
|
||||
// await page
|
||||
// .locator('span')
|
||||
// .filter({ hasText: 'AnzeigeneinstellungenFarbmodusSprache' })
|
||||
// .getByRole('button')
|
||||
// .click();
|
||||
// await page
|
||||
// .locator('span')
|
||||
// .filter({ hasText: 'AnzeigeneinstellungenFarbmodusSprache' })
|
||||
// .getByRole('button')
|
||||
// .click();
|
||||
// await page.getByRole('tab', { name: 'Dashboard' }).click();
|
||||
// await page.waitForURL('**/web/home');
|
||||
// });
|
||||
|
||||
await page.getByRole('tab', { name: 'Dashboard' }).click();
|
||||
await page.waitForURL('**/web/home');
|
||||
});
|
||||
test('Settings - User theme', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'allaccess',
|
||||
password: 'nolimits'
|
||||
});
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
test('Settings - User theme', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
await page.getByRole('button', { name: 'Ally Access' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||
|
||||
@ -82,14 +78,14 @@ test('Settings - User theme', async ({ page }) => {
|
||||
// primary
|
||||
await page.getByLabel('#fab005').click();
|
||||
await page.getByLabel('#228be6').click();
|
||||
|
||||
// language
|
||||
await page.getByRole('button', { name: 'Use pseudo language' }).click();
|
||||
});
|
||||
|
||||
test('Settings - Admin', async ({ page }) => {
|
||||
test('Settings - Admin', async ({ browser }) => {
|
||||
// Note here we login with admin access
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// User settings
|
||||
await page.getByRole('button', { name: 'admin' }).click();
|
||||
@ -184,9 +180,12 @@ test('Settings - Admin', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
});
|
||||
|
||||
test('Settings - Admin - Barcode History', async ({ page, request }) => {
|
||||
test('Settings - Admin - Barcode History', async ({ browser, request }) => {
|
||||
// Login with admin credentials
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
|
||||
// Ensure that the "save scans" setting is enabled
|
||||
await setSettingState({
|
||||
@ -221,11 +220,14 @@ test('Settings - Admin - Barcode History', async ({ page, request }) => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Settings - Admin - Unauthorized', async ({ page }) => {
|
||||
test('Settings - Admin - Unauthorized', async ({ browser }) => {
|
||||
// Try to access "admin" page with a non-staff user
|
||||
await doQuickLogin(page, 'allaccess', 'nolimits');
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'allaccess',
|
||||
password: 'nolimits',
|
||||
url: 'settings/admin/'
|
||||
});
|
||||
|
||||
await navigate(page, 'settings/admin/');
|
||||
await page.waitForURL('**/settings/admin/**');
|
||||
|
||||
// Should get a permission denied message
|
||||
@ -252,9 +254,12 @@ test('Settings - Admin - Unauthorized', async ({ page }) => {
|
||||
});
|
||||
|
||||
// Test for user auth configuration
|
||||
test('Settings - Auth - Email', async ({ page }) => {
|
||||
await doQuickLogin(page, 'allaccess', 'nolimits');
|
||||
await navigate(page, 'settings/user/');
|
||||
test('Settings - Auth - Email', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'allaccess',
|
||||
password: 'nolimits',
|
||||
url: 'settings/user/'
|
||||
});
|
||||
|
||||
await loadTab(page, 'Security');
|
||||
|
||||
@ -269,9 +274,8 @@ test('Settings - Auth - Email', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Remove' }).click();
|
||||
|
||||
await page.getByText('Currently no email addresses are registered').waitFor();
|
||||
|
||||
await page.waitForTimeout(2500);
|
||||
});
|
||||
|
||||
async function testColorPicker(page, ref: string) {
|
||||
const element = page.getByLabel(ref);
|
||||
await element.click();
|
||||
|
@ -4,13 +4,11 @@ import {
|
||||
navigate,
|
||||
setTableChoiceFilter
|
||||
} from './helpers.js';
|
||||
import { doQuickLogin } from './login.js';
|
||||
|
||||
test('Tables - Filters', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
import { doCachedLogin } from './login.js';
|
||||
|
||||
test('Tables - Filters', async ({ browser }) => {
|
||||
// Head to the "build order list" page
|
||||
await navigate(page, 'manufacturing/index/');
|
||||
const page = await doCachedLogin(browser, { url: 'manufacturing/index/' });
|
||||
|
||||
await clearTableFilters(page);
|
||||
|
||||
@ -41,11 +39,11 @@ test('Tables - Filters', async ({ page }) => {
|
||||
await clearTableFilters(page);
|
||||
});
|
||||
|
||||
test('Tables - Columns', async ({ page }) => {
|
||||
await doQuickLogin(page);
|
||||
|
||||
test('Tables - Columns', async ({ browser }) => {
|
||||
// Go to the "stock list" page
|
||||
await navigate(page, 'stock/location/index/stock-items');
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'stock/location/index/stock-items'
|
||||
});
|
||||
|
||||
// Open column selector
|
||||
await page.getByLabel('table-select-columns').click();
|
||||
@ -64,6 +62,4 @@ test('Tables - Columns', async ({ page }) => {
|
||||
await page.getByRole('menuitem', { name: 'Target Date' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Reference', exact: true }).click();
|
||||
await page.getByRole('menuitem', { name: 'Project Code' }).click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { test } from '../baseFixtures';
|
||||
import { navigate } from '../helpers';
|
||||
import { doQuickLogin } from '../login';
|
||||
import { doCachedLogin } from '../login';
|
||||
|
||||
test('PUI - Admin - Parameter', async ({ page }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
test('PUI - Admin - Parameter', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
username: 'admin',
|
||||
password: 'inventree'
|
||||
});
|
||||
await page.getByRole('button', { name: 'admin' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Admin Center' }).click();
|
||||
await page.getByRole('tab', { name: 'Part Parameters' }).click();
|
||||
|
@ -6,66 +6,77 @@ import license from 'rollup-plugin-license';
|
||||
import { defineConfig, splitVendorChunkPlugin } from 'vite';
|
||||
import istanbul from 'vite-plugin-istanbul';
|
||||
|
||||
// Detect if the current environment is WSL
|
||||
// Required for enabling file system polling
|
||||
const IS_IN_WSL = platform().includes('WSL') || release().includes('WSL');
|
||||
const is_coverage = process.env.VITE_COVERAGE === 'true';
|
||||
|
||||
// Detect if code coverage is enabled (runs in GitHub CI)
|
||||
const IS_COVERAGE = !!process.env.VITE_COVERAGE_BUILD;
|
||||
|
||||
if (IS_IN_WSL) {
|
||||
console.log('WSL detected: using polling for file system events');
|
||||
}
|
||||
|
||||
// Output directory for the built files
|
||||
const OUTPUT_DIR = '../../src/backend/InvenTree/web/static/web';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: ['macros']
|
||||
}
|
||||
}),
|
||||
vanillaExtractPlugin(),
|
||||
splitVendorChunkPlugin(),
|
||||
license({
|
||||
sourcemap: true,
|
||||
thirdParty: {
|
||||
includePrivate: true,
|
||||
multipleVersions: true,
|
||||
output: {
|
||||
file: '../backend/InvenTree/web/static/web/.vite/dependencies.json',
|
||||
template(dependencies) {
|
||||
return JSON.stringify(dependencies);
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
return {
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: ['macros']
|
||||
}
|
||||
}),
|
||||
vanillaExtractPlugin(),
|
||||
splitVendorChunkPlugin(),
|
||||
license({
|
||||
sourcemap: true,
|
||||
thirdParty: {
|
||||
includePrivate: true,
|
||||
multipleVersions: true,
|
||||
output: {
|
||||
file: '../backend/InvenTree/web/static/web/.vite/dependencies.json',
|
||||
template(dependencies) {
|
||||
return JSON.stringify(dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
istanbul({
|
||||
include: 'src/*',
|
||||
exclude: ['node_modules', 'test/'],
|
||||
extension: ['.js', '.ts', '.tsx'],
|
||||
requireEnv: true
|
||||
}),
|
||||
codecovVitePlugin({
|
||||
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
|
||||
bundleName: 'pui_v1',
|
||||
uploadToken: process.env.CODECOV_TOKEN
|
||||
})
|
||||
],
|
||||
base: '',
|
||||
build: {
|
||||
manifest: true,
|
||||
outDir: '../../src/backend/InvenTree/web/static/web',
|
||||
sourcemap: is_coverage
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/media': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
secure: true
|
||||
}
|
||||
}),
|
||||
istanbul({
|
||||
include: 'src/*',
|
||||
exclude: ['node_modules', 'test/'],
|
||||
extension: ['.js', '.ts', '.tsx'],
|
||||
requireEnv: true
|
||||
}),
|
||||
codecovVitePlugin({
|
||||
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
|
||||
bundleName: 'pui_v1',
|
||||
uploadToken: process.env.CODECOV_TOKEN
|
||||
})
|
||||
],
|
||||
// When building, set the base path to an empty string
|
||||
// This is required to ensure that the static path prefix is observed
|
||||
base: command == 'build' ? '' : undefined,
|
||||
build: {
|
||||
manifest: true,
|
||||
outDir: OUTPUT_DIR,
|
||||
sourcemap: IS_COVERAGE
|
||||
},
|
||||
watch: {
|
||||
// use polling only for WSL as the file system doesn't trigger notifications for Linux apps
|
||||
// ref: https://github.com/vitejs/vite/issues/1153#issuecomment-785467271
|
||||
usePolling: IS_IN_WSL
|
||||
server: {
|
||||
proxy: {
|
||||
'/media': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
secure: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// Use polling only for WSL as the file system doesn't trigger notifications for Linux apps
|
||||
// Ref: https://github.com/vitejs/vite/issues/1153#issuecomment-785467271
|
||||
usePolling: IS_IN_WSL
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -1519,6 +1519,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz#775374306116d51c0c500b8c4face0f9a04752d8"
|
||||
integrity sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==
|
||||
|
||||
"@messageformat/date-skeleton@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz#3bad068cbf5873d14592cfc7a73dd4d8615e2739"
|
||||
integrity sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==
|
||||
|
||||
"@messageformat/parser@^5.0.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.0.tgz#05e4851c782d633ad735791dd0a68ee65d2a7201"
|
||||
@ -1951,10 +1956,10 @@
|
||||
dependencies:
|
||||
undici-types "~6.19.2"
|
||||
|
||||
"@types/node@^22.6.0":
|
||||
version "22.10.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7"
|
||||
integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==
|
||||
"@types/node@^22.13.14":
|
||||
version "22.13.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.14.tgz#70d84ec91013dcd2ba2de35532a5a14c2b4cc912"
|
||||
integrity sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
@ -3312,6 +3317,11 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
|
||||
|
||||
inquirer@^7.3.3:
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
|
||||
@ -3996,6 +4006,14 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
path@^0.12.7:
|
||||
version "0.12.7"
|
||||
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
|
||||
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
|
||||
dependencies:
|
||||
process "^0.11.1"
|
||||
util "^0.10.3"
|
||||
|
||||
pathe@^1.1.0, pathe@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
|
||||
@ -4112,6 +4130,11 @@ process-on-spawn@^1.0.0:
|
||||
dependencies:
|
||||
fromentries "^1.2.0"
|
||||
|
||||
process@^0.11.1:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||
|
||||
prop-types@15.x, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
@ -4954,6 +4977,13 @@ util-deprecate@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
util@^0.10.3:
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
|
||||
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
|
||||
dependencies:
|
||||
inherits "2.0.3"
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
|
Loading…
x
Reference in New Issue
Block a user