mirror of https://github.com/jetkvm/kvm.git
fix
This commit is contained in:
parent
c563a28613
commit
2bd9867d25
|
|
@ -5,9 +5,16 @@ import { m } from "../localization/paraglide/messages.js";
|
|||
type I18nKey = keyof typeof m;
|
||||
type I18nOptions = Record<string, string>;
|
||||
|
||||
export const translate = (i18nKey: I18nKey, options?: I18nOptions) => (
|
||||
m[i18nKey as keyof typeof m] as (options?: I18nOptions) => string
|
||||
)(options);
|
||||
export const translate = (i18nKey: I18nKey, options?: I18nOptions) => {
|
||||
const func = m[i18nKey as keyof typeof m] as (options?: I18nOptions) => string
|
||||
const message = func(options);
|
||||
// older version uses "..." instead of "…" for the ellipsis
|
||||
// to avoid breaking the tests, we just remove the ellipsis
|
||||
if (message.includes("…")) {
|
||||
return message.replace("…", "");
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
export const getByText = (page: Page, i18nKey: I18nKey, options?: I18nOptions) => (
|
||||
page.getByText(translate(i18nKey, options))
|
||||
|
|
@ -17,4 +24,4 @@ export const getByRole = (page: Page, role: Parameters<typeof page.getByRole>[0]
|
|||
page.getByRole(role, { name: translate(i18nKey) })
|
||||
);
|
||||
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const sleep = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { test, expect, PlaywrightTestArgs } from '@playwright/test';
|
||||
import { test, expect, PlaywrightTestArgs, Page } from '@playwright/test';
|
||||
|
||||
import { m } from '../localization/paraglide/messages';
|
||||
|
||||
|
|
@ -6,122 +6,20 @@ import { getByRole, getByText, sleep } from './helper';
|
|||
|
||||
const TARGET_DEVICE_IP = "192.168.0.145"
|
||||
|
||||
test('upgrade process', async ({ page }) => {
|
||||
await page.goto(`http://${TARGET_DEVICE_IP}`);
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/JetKVM/);
|
||||
|
||||
// Except ICE gathering completed or JetKVM device connected
|
||||
await expect(getByText(page, 'ice_gathering_completed').or(getByText(page, 'peer_connection_connected'))).toBeVisible();
|
||||
|
||||
// Except No HDMI signal detected, as the device is not connected to the HDMI port
|
||||
await expect(getByText(page, 'video_overlay_no_hdmi_signal')).toBeVisible();
|
||||
|
||||
// Emulate upgrade process
|
||||
await getByRole(page, 'button', 'action_bar_settings').click();
|
||||
await expect(getByText(page, 'general_page_description')).toBeVisible();
|
||||
|
||||
const btnCheckForUpdates = getByRole(page, 'button', 'general_check_for_updates');
|
||||
await expect(btnCheckForUpdates).toBeVisible();
|
||||
await btnCheckForUpdates.click();
|
||||
|
||||
// if System is to update, we'll then skip the update process and go to the next step
|
||||
if (await getByText(page, 'general_update_up_to_date_title').isVisible()) {
|
||||
test.skip(true, 'System is up to date, skipping update process');
|
||||
return;
|
||||
}
|
||||
|
||||
// LoadingState -> Checking for updates...
|
||||
await expect(getByText(page, 'general_update_checking_title')).toBeVisible();
|
||||
|
||||
// UpdateAvailableState -> Prompt to update the device
|
||||
await expect(getByText(page, 'general_update_available_title')).toBeVisible();
|
||||
const btnUpdateNow = getByRole(page, 'button', 'general_update_now_button');
|
||||
await expect(btnUpdateNow).toBeVisible();
|
||||
await btnUpdateNow.click();
|
||||
|
||||
// LoadingState -> Updating your device...
|
||||
await expect(getByText(page, 'general_update_updating_title')).toBeVisible();
|
||||
const update_type = m.general_update_application_type();
|
||||
await expect(getByText(page, 'general_update_status_downloading', { update_type })).toBeVisible();
|
||||
await expect(getByText(page, 'general_update_status_verifying', { update_type })).toBeVisible();
|
||||
await expect(getByText(page, 'general_update_status_installing', { update_type })).toBeVisible();
|
||||
await expect(getByText(page, 'general_update_status_awaiting_reboot')).toBeVisible();
|
||||
|
||||
await page.waitForTimeout(30000);
|
||||
// await expect(page.getByText('Update available')).toBeVisible();
|
||||
|
||||
// await page.getByRole('button', { name: 'Update now' }).click();
|
||||
|
||||
// await expect(page.getByText('Updating...')).toBeVisible();
|
||||
|
||||
});
|
||||
|
||||
interface CustomUpgradeProcessOptions {
|
||||
sys?: string;
|
||||
app?: string;
|
||||
resetConfig?: boolean;
|
||||
}
|
||||
|
||||
const customUpgradeProcess = ({ sys, app }: CustomUpgradeProcessOptions) => async ({ page }: PlaywrightTestArgs) => {
|
||||
await page.goto(`http://${TARGET_DEVICE_IP}`);
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(
|
||||
page,
|
||||
'Title should include JetKVM',
|
||||
).toHaveTitle(/JetKVM/);
|
||||
|
||||
// If it's showing Welcome screen, we'll do the setup process,
|
||||
// otherwise, we'll skip the setup process and go to the next step
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (await getByText(page, 'welcome_to_jetkvm').isVisible()) {
|
||||
// check if current URL is /welcome
|
||||
await page.waitForURL('**/welcome');
|
||||
// click the setup button (LinkButton)
|
||||
await getByRole(page, 'link', 'jetkvm_setup').click();
|
||||
// check if current URL is /welcome/mode
|
||||
await page.waitForURL('**/welcome/mode');
|
||||
// click the local authentication method button
|
||||
await page.locator('input[name="localAuthMode"][value="noPassword"]').click();
|
||||
// wait for 1 second to ensure the checkbox is checked
|
||||
await sleep(1000);
|
||||
// click the continue button
|
||||
await getByRole(page, 'button', 'continue').click();
|
||||
}
|
||||
|
||||
// Except ICE gathering completed or JetKVM device connected
|
||||
await expect(
|
||||
getByText(page, 'ice_gathering_completed').
|
||||
or(getByText(page, 'peer_connection_connected').first()).
|
||||
or(getByText(page, 'video_overlay_loading_stream'))
|
||||
,
|
||||
'Wait until the WebRTC connection is established',
|
||||
).toBeVisible();
|
||||
|
||||
// Except No HDMI signal detected, as the device is not connected to the HDMI port
|
||||
// await expect(
|
||||
// getByText(page, 'video_overlay_no_hdmi_signal'),
|
||||
// 'should be visible when no HDMI signal is detected',
|
||||
// ).toBeVisible();
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// Emulate upgrade process
|
||||
await getByRole(page, 'button', 'action_bar_settings').click();
|
||||
await expect(
|
||||
getByText(page, 'general_page_description'),
|
||||
'should be visible when general page description is visible',
|
||||
).toBeVisible();
|
||||
|
||||
const doCustomUpdateSelect = async (page: Page, sys?: string, app?: string) => {
|
||||
// Go to Advanced tab
|
||||
const btnAdvanced = getByRole(page, 'link', 'settings_advanced');
|
||||
await expect(
|
||||
btnAdvanced,
|
||||
'should be visible when advanced link is visible',
|
||||
).toBeVisible();
|
||||
await sleep(1000);
|
||||
await sleep();
|
||||
await btnAdvanced.click();
|
||||
|
||||
// Now we're on the Advanced tab
|
||||
|
|
@ -187,11 +85,11 @@ const customUpgradeProcess = ({ sys, app }: CustomUpgradeProcessOptions) => asyn
|
|||
// now, click the damn button
|
||||
const btnVersionUpdate = getByRole(page, 'button', 'advanced_version_update_button');
|
||||
await expect(btnVersionUpdate).toBeVisible();
|
||||
await sleep(1000);
|
||||
await sleep();
|
||||
await btnVersionUpdate.click();
|
||||
|
||||
// Upgrade is a very time-consuming process, so we'll give it a generous timeout
|
||||
const timeout = 5 * 60 * 1000;
|
||||
// wait for 1 minute for the checkUpdate process to complete
|
||||
const timeout = 1 * 60 * 1000;
|
||||
|
||||
// LoadingState -> Checking for updates...
|
||||
await expect(getByText(page, 'general_update_checking_title'),
|
||||
|
|
@ -210,14 +108,137 @@ const customUpgradeProcess = ({ sys, app }: CustomUpgradeProcessOptions) => asyn
|
|||
await expect(btnUpdateNow,
|
||||
'Update Now button should be visible',
|
||||
).toBeVisible();
|
||||
await sleep(1000);
|
||||
await sleep();
|
||||
await btnUpdateNow.click();
|
||||
}
|
||||
|
||||
const doStandardUpdate = async (page: Page) => {
|
||||
const btnCheckForUpdates = getByRole(page, 'button', 'general_check_for_updates');
|
||||
await expect(btnCheckForUpdates).toBeVisible();
|
||||
await sleep();
|
||||
await btnCheckForUpdates.click();
|
||||
|
||||
// LoadingState -> Checking for updates...
|
||||
// sometimes it would be too fast to check for updates, so we just check both titles here
|
||||
await expect(
|
||||
getByText(page, 'general_update_checking_title').
|
||||
or(getByText(page, 'general_update_available_title')).
|
||||
or(getByText(page, 'general_update_up_to_date_title')),
|
||||
'LoadingState: checking for updates or update available',
|
||||
).toBeVisible();
|
||||
|
||||
// check if system is up to date
|
||||
if (await getByText(page, 'general_update_up_to_date_title').isVisible()) {
|
||||
test.skip(true, 'System is up to date, skipping update process');
|
||||
return;
|
||||
}
|
||||
|
||||
// UpdateAvailableState -> Prompt to update the device
|
||||
const btnUpdateNow = getByRole(page, 'button', 'general_update_now_button');
|
||||
await expect(btnUpdateNow).toBeVisible();
|
||||
|
||||
// Check which updates are going to be installed
|
||||
const updateAvailableLabel = page.locator('.mb-2.text-sm', { hasText: m.general_update_available_description() }).first();
|
||||
await expect(updateAvailableLabel).toBeVisible();
|
||||
|
||||
const updateAvailableContainer = page.locator('.text-left', { has: updateAvailableLabel }).first();
|
||||
await expect(updateAvailableContainer).toBeVisible();
|
||||
|
||||
const versionInfo = {
|
||||
sys: '',
|
||||
app: '',
|
||||
}
|
||||
|
||||
const systemUpdateAvailable = updateAvailableContainer.locator('.text-sm', { hasText: m.general_update_system_type()+":" }).first();
|
||||
if (await systemUpdateAvailable.isVisible()) {
|
||||
versionInfo.sys = await systemUpdateAvailable.textContent() ?? '';
|
||||
}
|
||||
|
||||
const appUpdateAvailable = updateAvailableContainer.locator('.text-sm', { hasText: m.general_update_application_type()+":" }).first();
|
||||
if (await appUpdateAvailable.isVisible()) {
|
||||
versionInfo.app = await appUpdateAvailable.textContent() ?? '';
|
||||
}
|
||||
|
||||
await sleep();
|
||||
await btnUpdateNow.click();
|
||||
|
||||
return versionInfo;
|
||||
}
|
||||
|
||||
const runUpdateTest = ({ sys, app }: CustomUpgradeProcessOptions) => async ({ page }: PlaywrightTestArgs) => {
|
||||
await page.goto(`http://${TARGET_DEVICE_IP}`);
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(
|
||||
page,
|
||||
'Title should include JetKVM',
|
||||
).toHaveTitle(/JetKVM/);
|
||||
|
||||
// If it's showing Welcome screen, we'll do the setup process,
|
||||
// otherwise, we'll skip the setup process and go to the next step
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (await getByText(page, 'welcome_to_jetkvm').isVisible()) {
|
||||
// check if current URL is /welcome
|
||||
await page.waitForURL('**/welcome');
|
||||
// click the setup button (LinkButton)
|
||||
await getByRole(page, 'link', 'jetkvm_setup').click();
|
||||
// check if current URL is /welcome/mode
|
||||
await page.waitForURL('**/welcome/mode');
|
||||
// click the local authentication method button
|
||||
await page.locator('input[name="localAuthMode"][value="noPassword"]').click();
|
||||
// wait for 1 second to ensure the checkbox is checked
|
||||
await sleep();
|
||||
// click the continue button
|
||||
await getByRole(page, 'button', 'continue').click();
|
||||
}
|
||||
|
||||
// Except ICE gathering completed or JetKVM device connected
|
||||
await expect(
|
||||
getByText(page, 'ice_gathering_completed').
|
||||
or(getByText(page, 'peer_connection_connected').first()).
|
||||
or(getByText(page, 'video_overlay_loading_stream'))
|
||||
,
|
||||
'Wait until the WebRTC connection is established',
|
||||
).toBeVisible();
|
||||
|
||||
// Except No HDMI signal detected, as the device is not connected to the HDMI port
|
||||
// await expect(
|
||||
// getByText(page, 'video_overlay_no_hdmi_signal'),
|
||||
// 'should be visible when no HDMI signal is detected',
|
||||
// ).toBeVisible();
|
||||
|
||||
|
||||
await sleep();
|
||||
|
||||
// Emulate upgrade process
|
||||
await getByRole(page, 'button', 'action_bar_settings').click();
|
||||
await expect(
|
||||
getByText(page, 'general_page_description'),
|
||||
'should be visible when general page description is visible',
|
||||
).toBeVisible();
|
||||
|
||||
let [sysUpdatePending, appUpdatePending] = [false, false];
|
||||
|
||||
// If sys or app is provided, we'll do a custom update, otherwise, we'll do a standard update
|
||||
if (sys || app) {
|
||||
await doCustomUpdateSelect(page, sys, app);
|
||||
sysUpdatePending = sys !== undefined;
|
||||
appUpdatePending = app !== undefined;
|
||||
} else {
|
||||
const versionInfo = await doStandardUpdate(page);
|
||||
sysUpdatePending = versionInfo?.sys !== '';
|
||||
appUpdatePending = versionInfo?.app !== '';
|
||||
}
|
||||
|
||||
// LoadingState -> Updating your device...
|
||||
await expect(getByText(page, 'general_update_updating_title'),
|
||||
'UpdatingDeviceState: title should be updating your device...',
|
||||
).toBeVisible();
|
||||
|
||||
// update is a very time-consuming process, so we'll give it a generous timeout
|
||||
const timeout = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
// UpdatingDeviceState -> Downloading, Verifying, Installing, Awaiting reboot or rebooting
|
||||
const waitingForUpdate = async (update_type: string = m.general_update_system_type()) => {
|
||||
await expect(
|
||||
|
|
@ -228,14 +249,16 @@ const customUpgradeProcess = ({ sys, app }: CustomUpgradeProcessOptions) => asyn
|
|||
).toBeVisible({ timeout });
|
||||
await expect(
|
||||
getByText(page, 'general_update_status_awaiting_reboot')
|
||||
.or(getByText(page, 'general_update_rebooting')),
|
||||
.or(getByText(page, 'general_update_rebooting'))
|
||||
// older version uses "..." instead of "…" for the ellipsis
|
||||
.or(page.getByText('Rebooting to complete the update')),
|
||||
'UpdatingDeviceState: awaiting reboot or rebooting',
|
||||
).toBeVisible({ timeout });
|
||||
};
|
||||
|
||||
// Application update always comes first
|
||||
if (app) await waitingForUpdate(m.general_update_application_type());
|
||||
if (sys) await waitingForUpdate(m.general_update_system_type());
|
||||
if (appUpdatePending) await waitingForUpdate(m.general_update_application_type());
|
||||
if (sysUpdatePending) await waitingForUpdate(m.general_update_system_type());
|
||||
|
||||
// Leaving device on
|
||||
// await expect(pageGetByText('updating_leave_device_on')).toBeVisible({ timeout });
|
||||
|
|
@ -277,16 +300,17 @@ const customUpgradeProcess = ({ sys, app }: CustomUpgradeProcessOptions) => asyn
|
|||
).toBeVisible({ timeout });
|
||||
};
|
||||
|
||||
test('standard upgrade process', runUpdateTest({}));
|
||||
|
||||
test('custom upgrade process: upgrade system only', customUpgradeProcess({
|
||||
test('custom upgrade process: upgrade system only', runUpdateTest({
|
||||
sys: '0.2.7',
|
||||
}));
|
||||
|
||||
test('custom upgrade process: upgrade app only', customUpgradeProcess({
|
||||
test('custom upgrade process: upgrade app only', runUpdateTest({
|
||||
app: '0.4.5',
|
||||
}));
|
||||
|
||||
test('custom upgrade process: upgrade both', customUpgradeProcess({
|
||||
test('custom upgrade process: upgrade both', runUpdateTest({
|
||||
sys: '0.2.7',
|
||||
app: '0.4.5',
|
||||
app: '0.4.8',
|
||||
}));
|
||||
Loading…
Reference in New Issue