mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-07 06:00:57 +00:00
[bug] Playwright fixes (#9933)
* Fixes for playwright testing - Ensure cookies are completely cleaned between sessions - Fix base URL based on vite command - Fix samesite cookie mode - Prevent /static/ files being served by web server on :8000 * Remove gunicorn option * Readjust base URL * Simplify doCachedLogin * Fix logic func * Revert webserver cmd * Set base URL in playwrightconfig file * Fix URL checks * Fix URL definitions * adjust playwright base URL * Tweak for URL helper * Further login tweaks * Tweak test * wait for API before starting tests * Handle error * Adjust login functions * Don't use gunicorn - But still use the webserver to serve static files in CI * Enhanced login functions * Tweak login tests * Fix broken test * Flipped the flippies
This commit is contained in:
@ -3,9 +3,6 @@ import { defineConfig, devices } from '@playwright/test';
|
|||||||
// Detect if running in CI
|
// Detect if running in CI
|
||||||
const IS_CI = !!process.env.CI;
|
const IS_CI = !!process.env.CI;
|
||||||
|
|
||||||
console.log('Running Playwright tests:');
|
|
||||||
console.log(` - CI Mode: ${IS_CI}`);
|
|
||||||
|
|
||||||
const MAX_WORKERS: number = 3;
|
const MAX_WORKERS: number = 3;
|
||||||
const MAX_RETRIES: number = 3;
|
const MAX_RETRIES: number = 3;
|
||||||
|
|
||||||
@ -20,7 +17,6 @@ const MAX_RETRIES: number = 3;
|
|||||||
* - In CI (GitHub actions), we run "vite build" to generate a production build
|
* - 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 build is then served by a local server for testing
|
||||||
* - This allows the tests to run much faster and with parallel workers
|
* - 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)
|
* - WORKERS = MAX_WORKERS (to speed up the tests)
|
||||||
*
|
*
|
||||||
* CI Mode (Coverage):
|
* CI Mode (Coverage):
|
||||||
@ -30,11 +26,13 @@ const MAX_RETRIES: number = 3;
|
|||||||
* - WORKERS = 1 (to avoid conflicts with HMR)
|
* - WORKERS = 1 (to avoid conflicts with HMR)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Command to spin-up the backend server
|
const BASE_URL: string = IS_CI
|
||||||
// In production mode, we want a stronger webserver to handle multiple requests
|
? 'http://localhost:8000'
|
||||||
const WEB_SERVER_CMD: string = IS_CI
|
: 'http://localhost:5173';
|
||||||
? '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';
|
console.log('Running Playwright Tests:');
|
||||||
|
console.log(`- CI Mode: ${IS_CI}`);
|
||||||
|
console.log('- Base URL:', BASE_URL);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './tests',
|
testDir: './tests',
|
||||||
@ -72,15 +70,16 @@ export default defineConfig({
|
|||||||
timeout: 120 * 1000
|
timeout: 120 * 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command: WEB_SERVER_CMD,
|
command: 'invoke dev.server',
|
||||||
env: {
|
env: {
|
||||||
INVENTREE_DEBUG: 'True',
|
INVENTREE_DEBUG: 'True',
|
||||||
|
INVENTREE_LOG_LEVEL: 'WARNING',
|
||||||
INVENTREE_PLUGINS_ENABLED: 'True',
|
INVENTREE_PLUGINS_ENABLED: 'True',
|
||||||
INVENTREE_ADMIN_URL: 'test-admin',
|
INVENTREE_ADMIN_URL: 'test-admin',
|
||||||
INVENTREE_SITE_URL: 'http://localhost:8000',
|
INVENTREE_SITE_URL: 'http://localhost:8000',
|
||||||
INVENTREE_FRONTEND_API_HOST: 'http://localhost:8000',
|
INVENTREE_FRONTEND_API_HOST: 'http://localhost:8000',
|
||||||
INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
|
INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
|
||||||
INVENTREE_COOKIE_SAMESITE: 'Lax',
|
INVENTREE_COOKIE_SAMESITE: 'False',
|
||||||
INVENTREE_LOGIN_ATTEMPTS: '100'
|
INVENTREE_LOGIN_ATTEMPTS: '100'
|
||||||
},
|
},
|
||||||
url: 'http://localhost:8000/api/',
|
url: 'http://localhost:8000/api/',
|
||||||
@ -92,7 +91,7 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
globalSetup: './playwright/global-setup.ts',
|
globalSetup: './playwright/global-setup.ts',
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:5173',
|
baseURL: BASE_URL,
|
||||||
headless: IS_CI ? true : undefined,
|
headless: IS_CI ? true : undefined,
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { type FullConfig, chromium } from '@playwright/test';
|
import { type FullConfig, chromium, request } from '@playwright/test';
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { apiUrl } from '../tests/defaults';
|
||||||
import { doCachedLogin } from '../tests/login';
|
import { doCachedLogin } from '../tests/login';
|
||||||
|
|
||||||
async function globalSetup(config: FullConfig) {
|
async function globalSetup(config: FullConfig) {
|
||||||
@ -18,27 +19,53 @@ async function globalSetup(config: FullConfig) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform login for each user
|
const baseUrl = config.projects[0].use?.baseURL || 'http://localhost:5173';
|
||||||
const browser = await chromium.launch();
|
const apiContext = await request.newContext();
|
||||||
|
|
||||||
await doCachedLogin(browser, {
|
let tries = 100;
|
||||||
|
let success = false;
|
||||||
|
|
||||||
|
// Wait for the web server to actually be started
|
||||||
|
while (tries--) {
|
||||||
|
// Perform GET request to the API URL
|
||||||
|
const response = await apiContext
|
||||||
|
.get(apiUrl, { timeout: 5000 })
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
if (!!response && response?.ok() && response?.status() === 200) {
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log(`... waiting for API to be available at ${apiUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`Failed to connect to API at ${apiUrl} after 100 attempts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform login for each user (each in a separate browser instance)
|
||||||
|
await doCachedLogin(await chromium.launch(), {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: 'inventree'
|
password: 'inventree',
|
||||||
|
baseUrl: baseUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
await doCachedLogin(browser, {
|
await doCachedLogin(await chromium.launch(), {
|
||||||
username: 'allaccess',
|
username: 'allaccess',
|
||||||
password: 'nolimits'
|
password: 'nolimits',
|
||||||
|
baseUrl: baseUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
await doCachedLogin(browser, {
|
await doCachedLogin(await chromium.launch(), {
|
||||||
username: 'reader',
|
username: 'reader',
|
||||||
password: 'readonly'
|
password: 'readonly',
|
||||||
|
baseUrl: baseUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
await doCachedLogin(browser, {
|
await doCachedLogin(await chromium.launch(), {
|
||||||
username: 'steven',
|
username: 'steven',
|
||||||
password: 'wizardstaff'
|
password: 'wizardstaff',
|
||||||
|
baseUrl: baseUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export const test = baseTest.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Ensure no errors are thrown in the console
|
// Ensure no errors are thrown in the console
|
||||||
page: async ({ baseURL, page }, use) => {
|
page: async ({ page }, use) => {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
page.on('console', (msg) => {
|
page.on('console', (msg) => {
|
||||||
const url = msg.location().url;
|
const url = msg.location().url;
|
||||||
@ -67,20 +67,20 @@ export const test = baseTest.extend({
|
|||||||
) < 0 &&
|
) < 0 &&
|
||||||
msg.text() !=
|
msg.text() !=
|
||||||
'Failed to load resource: the server responded with a status of 400 (Bad Request)' &&
|
'Failed to load resource: the server responded with a status of 400 (Bad Request)' &&
|
||||||
!msg.text().includes('http://localhost:8000/this/does/not/exist.js') &&
|
!msg.text().includes('/this/does/not/exist.js') &&
|
||||||
url != 'http://localhost:8000/this/does/not/exist.js' &&
|
!url.includes('/this/does/not/exist.js') &&
|
||||||
url != 'http://localhost:8000/api/user/me/' &&
|
!url.includes('/api/user/me/') &&
|
||||||
url != 'http://localhost:8000/api/user/token/' &&
|
!url.includes('/api/user/token/') &&
|
||||||
url != 'http://localhost:8000/api/auth/v1/auth/login' &&
|
!url.includes('/api/auth/v1/auth/login') &&
|
||||||
url != 'http://localhost:8000/api/auth/v1/auth/session' &&
|
!url.includes('/api/auth/v1/auth/session') &&
|
||||||
url != 'http://localhost:8000/api/auth/v1/account/password/change' &&
|
!url.includes('/api/auth/v1/account/password/change') &&
|
||||||
url != 'http://localhost:8000/api/barcode/' &&
|
!url.includes('/api/barcode/') &&
|
||||||
url != 'https://docs.inventree.org/en/versions.json' &&
|
!url.includes('/favicon.ico') &&
|
||||||
url != 'http://localhost:5173/favicon.ico' &&
|
|
||||||
!url.startsWith('https://api.github.com/repos/inventree') &&
|
!url.startsWith('https://api.github.com/repos/inventree') &&
|
||||||
!url.startsWith('http://localhost:8000/api/news/') &&
|
!url.includes('/api/news/') &&
|
||||||
!url.startsWith('http://localhost:8000/api/notifications/') &&
|
!url.includes('/api/notifications/') &&
|
||||||
!url.startsWith('chrome://') &&
|
!url.startsWith('chrome://') &&
|
||||||
|
url != 'https://docs.inventree.org/en/versions.json' &&
|
||||||
url.indexOf('99999') < 0
|
url.indexOf('99999') < 0
|
||||||
)
|
)
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
export const classicUrl = 'http://127.0.0.1:8000';
|
export const webUrl = '/web';
|
||||||
|
|
||||||
export const apiUrl = `${classicUrl}/api`;
|
// Note: API requests are handled by the backend server
|
||||||
export const baseUrl = './web';
|
export const apiUrl = 'http://localhost:8000/api/';
|
||||||
export const loginUrl = `${baseUrl}/login`;
|
|
||||||
export const logoutUrl = `${baseUrl}/logout`;
|
export const homeUrl = `${webUrl}/home`;
|
||||||
export const homeUrl = `${baseUrl}/home`;
|
export const loginUrl = `${webUrl}/login`;
|
||||||
|
export const logoutUrl = `${webUrl}/logout`;
|
||||||
|
|
||||||
export const user = {
|
export const user = {
|
||||||
name: 'Ally Access',
|
name: 'Ally Access',
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { baseUrl } from './defaults';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the filter drawer for the currently visible table
|
* Open the filter drawer for the currently visible table
|
||||||
* @param page - The page object
|
* @param page - The page object
|
||||||
@ -73,21 +71,30 @@ export const clickOnRowMenu = async (cell) => {
|
|||||||
await row.getByLabel(/row-action-menu-/i).click();
|
await row.getByLabel(/row-action-menu-/i).click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface NavigateOptions {
|
||||||
|
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
|
||||||
|
baseUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the provided page, and wait for loading to complete
|
* Navigate to the provided page, and wait for loading to complete
|
||||||
* @param page
|
* @param page
|
||||||
* @param url
|
* @param url
|
||||||
*/
|
*/
|
||||||
export const navigate = async (page, url: string) => {
|
export const navigate = async (
|
||||||
if (!url.startsWith(baseUrl)) {
|
page,
|
||||||
if (url.startsWith('/')) {
|
url: string,
|
||||||
url = url.slice(1);
|
options?: NavigateOptions
|
||||||
|
) => {
|
||||||
|
if (!url.startsWith('http') && !url.includes('web')) {
|
||||||
|
url = `/web/${url}`.replaceAll('//', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
url = `${baseUrl}/${url}`;
|
const path: string = options?.baseUrl
|
||||||
}
|
? new URL(url, options.baseUrl).toString()
|
||||||
|
: url;
|
||||||
|
|
||||||
await page.goto(url);
|
await page.goto(path, { waitUntil: options?.waitUntil ?? 'load' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,33 +1,53 @@
|
|||||||
import type { Browser, Page } from '@playwright/test';
|
import type { Browser, Page } from '@playwright/test';
|
||||||
import { expect } from './baseFixtures.js';
|
import { loginUrl, logoutUrl, user, webUrl } from './defaults';
|
||||||
import { user } from './defaults';
|
|
||||||
import { navigate } from './helpers.js';
|
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { navigate } from './helpers.js';
|
||||||
|
|
||||||
|
interface LoginOptions {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Perform form based login operation from the "login" URL
|
* Perform form based login operation from the "login" URL
|
||||||
*/
|
*/
|
||||||
export const doLogin = async (page, username?: string, password?: string) => {
|
export const doLogin = async (page, options?: LoginOptions) => {
|
||||||
username = username ?? user.username;
|
const username: string = options?.username ?? user.username;
|
||||||
password = password ?? user.password;
|
const password: string = options?.password ?? user.password;
|
||||||
|
|
||||||
await page.goto('http://localhost:8000/web/logout', { waituntil: 'load' });
|
console.log('- Logging in with username:', username);
|
||||||
|
|
||||||
|
await navigate(page, loginUrl, {
|
||||||
|
baseUrl: options?.baseUrl,
|
||||||
|
waitUntil: 'networkidle'
|
||||||
|
});
|
||||||
|
|
||||||
await expect(page).toHaveTitle(/^InvenTree.*$/);
|
|
||||||
await page.waitForURL('**/web/login');
|
await page.waitForURL('**/web/login');
|
||||||
|
|
||||||
await page.getByLabel('username').fill(username);
|
await page.getByLabel('username').fill(username);
|
||||||
await page.getByLabel('password').fill(password);
|
await page.getByLabel('password').fill(password);
|
||||||
|
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Log in' }).click();
|
await page.getByRole('button', { name: 'Log in' }).click();
|
||||||
await page.waitForURL('**/web/home');
|
|
||||||
await page.waitForTimeout(250);
|
await page.waitForTimeout(100);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
await page.getByRole('link', { name: 'Dashboard' }).waitFor();
|
||||||
|
await page.getByRole('button', { name: 'navigation-menu' }).waitFor();
|
||||||
|
await page.waitForURL(/\/web(\/home)?/);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CachedLoginOptions {
|
export interface CachedLoginOptions {
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
baseUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set of users allowed to do cached login
|
// Set of users allowed to do cached login
|
||||||
@ -58,9 +78,17 @@ export const doCachedLogin = async (
|
|||||||
storageState: fn
|
storageState: fn
|
||||||
});
|
});
|
||||||
console.log(`Using cached login state for ${username}`);
|
console.log(`Using cached login state for ${username}`);
|
||||||
await navigate(page, url);
|
|
||||||
await page.waitForURL('**/web/**');
|
await navigate(page, url ?? webUrl, {
|
||||||
await page.waitForLoadState('load');
|
baseUrl: options?.baseUrl,
|
||||||
|
waitUntil: 'networkidle'
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('link', { name: 'Dashboard' }).waitFor();
|
||||||
|
await page.getByRole('button', { name: 'navigation-menu' }).waitFor();
|
||||||
|
await page.waitForURL(/\/web(\/home)?/);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,29 +97,37 @@ export const doCachedLogin = async (
|
|||||||
|
|
||||||
console.log(`No cache found - logging in for ${username}`);
|
console.log(`No cache found - logging in for ${username}`);
|
||||||
|
|
||||||
// Ensure we start from the login page
|
// Completely clear the browser cache and cookies, etc
|
||||||
await page.goto('http://localhost:8000/web/', { waitUntil: 'load' });
|
await page.context().clearCookies();
|
||||||
|
await page.context().clearPermissions();
|
||||||
|
|
||||||
|
await doLogin(page, {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
baseUrl: options?.baseUrl
|
||||||
|
});
|
||||||
|
|
||||||
await doLogin(page, username, password);
|
|
||||||
await page.getByLabel('navigation-menu').waitFor({ timeout: 5000 });
|
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.waitForLoadState('load');
|
await page.waitForLoadState('load');
|
||||||
|
|
||||||
// Cache the login state
|
// Cache the login state
|
||||||
await page.context().storageState({ path: fn });
|
await page.context().storageState({ path: fn });
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
await navigate(page, url);
|
await navigate(page, url, { baseUrl: options?.baseUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doLogout = async (page) => {
|
interface LogoutOptions {
|
||||||
await page.goto('http://localhost:8000/web/logout', { waitUntil: 'load' });
|
baseUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const doLogout = async (page, options?: LogoutOptions) => {
|
||||||
|
await navigate(page, logoutUrl, {
|
||||||
|
baseUrl: options?.baseUrl,
|
||||||
|
waitUntil: 'load'
|
||||||
|
});
|
||||||
await page.waitForURL('**/web/login');
|
await page.waitForURL('**/web/login');
|
||||||
};
|
};
|
||||||
|
@ -265,7 +265,7 @@ test('Parts - Pricing (Nothing, BOM)', async ({ browser }) => {
|
|||||||
await page.getByRole('button', { name: 'Supplier Pricing' }).isDisabled();
|
await page.getByRole('button', { name: 'Supplier Pricing' }).isDisabled();
|
||||||
|
|
||||||
// Part with history
|
// Part with history
|
||||||
await navigate(page, 'part/108/pricing');
|
await navigate(page, 'part/108/pricing', { waitUntil: 'networkidle' });
|
||||||
await page.getByText('A chair - with blue paint').waitFor();
|
await page.getByText('A chair - with blue paint').waitFor();
|
||||||
await loadTab(page, 'Part Pricing');
|
await loadTab(page, 'Part Pricing');
|
||||||
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
|
||||||
|
@ -45,13 +45,13 @@ test('Login - Failures', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Login - Change Password', async ({ page }) => {
|
test('Login - Change Password', async ({ page }) => {
|
||||||
await doLogin(page, 'noaccess', 'youshallnotpass');
|
await doLogin(page, {
|
||||||
await page.waitForLoadState('networkidle');
|
username: 'noaccess',
|
||||||
|
password: 'youshallnotpass'
|
||||||
|
});
|
||||||
|
|
||||||
// Navigate to the 'change password' page
|
// Navigate to the 'change password' page
|
||||||
await navigate(page, 'settings/user/account');
|
await navigate(page, 'settings/user/account', { waitUntil: 'networkidle' });
|
||||||
await page.waitForLoadState('networkidle');
|
|
||||||
|
|
||||||
await page.getByLabel('action-menu-account-actions').click();
|
await page.getByLabel('action-menu-account-actions').click();
|
||||||
await page.getByLabel('action-menu-account-actions-change-password').click();
|
await page.getByLabel('action-menu-account-actions-change-password').click();
|
||||||
|
|
||||||
@ -69,9 +69,16 @@ test('Login - Change Password', async ({ page }) => {
|
|||||||
await page.getByText('This password is too short').waitFor();
|
await page.getByText('This password is too short').waitFor();
|
||||||
await page.getByText('This password is entirely numeric').waitFor();
|
await page.getByText('This password is entirely numeric').waitFor();
|
||||||
|
|
||||||
await page.getByLabel('input-password-1').fill('youshallnotpass');
|
await page.waitForTimeout(250);
|
||||||
|
|
||||||
|
await page.getByLabel('password', { exact: true }).clear();
|
||||||
|
await page.getByLabel('input-password-1').clear();
|
||||||
await page.getByLabel('input-password-2').clear();
|
await page.getByLabel('input-password-2').clear();
|
||||||
|
|
||||||
|
await page.getByLabel('password', { exact: true }).fill('youshallnotpass');
|
||||||
|
await page.getByLabel('input-password-1').fill('youshallnotpass');
|
||||||
await page.getByLabel('input-password-2').fill('youshallnotpass');
|
await page.getByLabel('input-password-2').fill('youshallnotpass');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||||
|
|
||||||
await page.getByText('Password Changed').waitFor();
|
await page.getByText('Password Changed').waitFor();
|
||||||
|
@ -198,7 +198,8 @@ test('Settings - Admin - Barcode History', async ({ browser, request }) => {
|
|||||||
const barcodes = ['ABC1234', 'XYZ5678', 'QRS9012'];
|
const barcodes = ['ABC1234', 'XYZ5678', 'QRS9012'];
|
||||||
|
|
||||||
barcodes.forEach(async (barcode) => {
|
barcodes.forEach(async (barcode) => {
|
||||||
await request.post(`${apiUrl}/barcode/`, {
|
const url = new URL('barcode/', apiUrl).toString();
|
||||||
|
await request.post(url, {
|
||||||
data: {
|
data: {
|
||||||
barcode: barcode
|
barcode: barcode
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@ export const setSettingState = async ({
|
|||||||
setting: string;
|
setting: string;
|
||||||
value: any;
|
value: any;
|
||||||
}) => {
|
}) => {
|
||||||
const url = `${apiUrl}/settings/global/${setting}/`;
|
const url = new URL(`settings/global/${setting}/`, apiUrl).toString();
|
||||||
|
|
||||||
const response = await request.patch(url, {
|
const response = await request.patch(url, {
|
||||||
data: {
|
data: {
|
||||||
@ -38,7 +38,7 @@ export const setPluginState = async ({
|
|||||||
plugin: string;
|
plugin: string;
|
||||||
state: boolean;
|
state: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const url = `${apiUrl}/plugins/${plugin}/activate/`;
|
const url = new URL(`plugins/${plugin}/activate/`, apiUrl).toString();
|
||||||
|
|
||||||
const response = await request.patch(url, {
|
const response = await request.patch(url, {
|
||||||
data: {
|
data: {
|
||||||
|
@ -21,6 +21,10 @@ const OUTPUT_DIR = '../../src/backend/InvenTree/web/static/web';
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ command, mode }) => {
|
export default defineConfig(({ command, mode }) => {
|
||||||
|
// In 'build' mode, we want to use an empty base URL (for static file generation)
|
||||||
|
const baseUrl: string | undefined = command === 'build' ? '' : undefined;
|
||||||
|
console.log(`Running Vite in '${command}' mode -> base URL: ${baseUrl}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
react({
|
react({
|
||||||
@ -57,7 +61,7 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
],
|
],
|
||||||
// When building, set the base path to an empty string
|
// When building, set the base path to an empty string
|
||||||
// This is required to ensure that the static path prefix is observed
|
// This is required to ensure that the static path prefix is observed
|
||||||
base: command == 'build' ? '' : undefined,
|
base: baseUrl,
|
||||||
build: {
|
build: {
|
||||||
manifest: true,
|
manifest: true,
|
||||||
outDir: OUTPUT_DIR,
|
outDir: OUTPUT_DIR,
|
||||||
|
Reference in New Issue
Block a user