mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Add playwright E2E tests (#2174)
This commit is contained in:
8
photon-client/.gitignore
vendored
8
photon-client/.gitignore
vendored
@@ -1,8 +0,0 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
components.d.ts
|
||||
@@ -8,7 +8,7 @@ export default defineConfigWithVueTs(
|
||||
vueTsConfigs.recommended,
|
||||
skipFormattingConfig,
|
||||
{
|
||||
ignores: ["**/dist/**"]
|
||||
ignores: ["**/dist/**", "playwright-report"]
|
||||
},
|
||||
{
|
||||
//extends: ["js/recommended"],
|
||||
|
||||
@@ -9,9 +9,13 @@
|
||||
"build": "vite build",
|
||||
"build-demo": "vite build --mode demo",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"format": "prettier --write src/",
|
||||
"format": "prettier --write src/ tests/",
|
||||
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
|
||||
"format-ci": "prettier --check src/"
|
||||
"format-ci": "prettier --check src/",
|
||||
"test": "playwright test",
|
||||
"test-ui": "playwright test --ui",
|
||||
"test-setup": "playwright install --with-deps"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/prompt": "^5.2.6",
|
||||
@@ -28,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@types/node": "^22.15.14",
|
||||
"@types/three": "^0.178.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
|
||||
83
photon-client/playwright.config.ts
Normal file
83
photon-client/playwright.config.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// import path from 'path';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/global-setup",
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('')`. */
|
||||
baseURL: "http://localhost:5800",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry"
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] }
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] }
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] }
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
use: { ...devices["Desktop Edge"], channel: "msedge" }
|
||||
}
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: process.platform == "win32" ? "" : "./" + "gradlew run",
|
||||
url: "http://localhost:5800",
|
||||
timeout: 300 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: path.normalize("../")
|
||||
}
|
||||
});
|
||||
38
photon-client/pnpm-lock.yaml
generated
38
photon-client/pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.31.0
|
||||
version: 9.31.0
|
||||
'@playwright/test':
|
||||
specifier: ^1.56.1
|
||||
version: 1.56.1
|
||||
'@types/node':
|
||||
specifier: ^22.15.14
|
||||
version: 22.15.14
|
||||
@@ -430,6 +433,11 @@ packages:
|
||||
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.19':
|
||||
resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==}
|
||||
|
||||
@@ -1019,6 +1027,11 @@ packages:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1247,6 +1260,16 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
playwright-core@1.56.1:
|
||||
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.56.1:
|
||||
resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
postcss-selector-parser@6.1.2:
|
||||
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1759,6 +1782,10 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.2.4': {}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
dependencies:
|
||||
playwright: 1.56.1
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.19': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.40.2':
|
||||
@@ -2399,6 +2426,9 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -2605,6 +2635,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
playwright-core@1.56.1: {}
|
||||
|
||||
playwright@1.56.1:
|
||||
dependencies:
|
||||
playwright-core: 1.56.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
postcss-selector-parser@6.1.2:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
|
||||
@@ -224,6 +224,7 @@ const handleBulkImport = () => {
|
||||
v-model="importVersion"
|
||||
variant="underlined"
|
||||
label="Model Version"
|
||||
data-testid="import-version-select"
|
||||
:items="
|
||||
useSettingsStore().general.supportedBackends?.includes('RKNN')
|
||||
? ['YOLOv5', 'YOLOv8', 'YOLO11']
|
||||
@@ -324,7 +325,7 @@ const handleBulkImport = () => {
|
||||
<th>Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody data-testid="model-table">
|
||||
<tr v-for="model in supportedModels" :key="model.modelPath">
|
||||
<td>{{ model.nickname }}</td>
|
||||
<td>{{ model.labels.join(", ") }}</td>
|
||||
|
||||
16
photon-client/tests/fixtures.ts
Normal file
16
photon-client/tests/fixtures.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test as base } from "@playwright/test";
|
||||
import axios from "axios";
|
||||
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
// Use the page in the test (no per-test backend reset here)
|
||||
axios.defaults.baseURL = "http://localhost:5800/api/test";
|
||||
await use(page);
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeAll(async () => {
|
||||
console.log("Running before all tests: Resetting backend state...");
|
||||
await axios.post("http://localhost:5800/api/test/resetBackend");
|
||||
await axios.post("http://localhost:5800/api/test/activateTestMode");
|
||||
});
|
||||
7
photon-client/tests/global-setup.ts
Normal file
7
photon-client/tests/global-setup.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
async function globalSetup() {
|
||||
// You can perform global setup tasks here, such as starting a server or setting environment variables
|
||||
const path = await import("path");
|
||||
process.env.TESTS_DIR = path.resolve(process.cwd());
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
79
photon-client/tests/platformDependent/od.spec.ts
Normal file
79
photon-client/tests/platformDependent/od.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "../fixtures";
|
||||
import axios from "axios";
|
||||
import path from "path";
|
||||
|
||||
const fakeModelName = "FAKE-MODEL";
|
||||
const fakeLabels = "test, 1, woof";
|
||||
const newModelName = "foo-bar";
|
||||
const platforms = ["LINUX_RK3588_64", "LINUX_QCS6490"];
|
||||
|
||||
for (const platform of platforms) {
|
||||
test.describe(`Platform: ${platform}`, () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/#/settings");
|
||||
await axios.post("/override/platform", { platform: platform });
|
||||
await page.reload();
|
||||
});
|
||||
|
||||
test("testSettingsPage", async ({ page }) => {
|
||||
if (platform.endsWith("RK3588_64")) {
|
||||
await expect(page.getByRole("main")).toContainText("Linux AARCH 64-bit with RK3588");
|
||||
} else if (platform.endsWith("QCS6490")) {
|
||||
await expect(page.getByRole("main")).toContainText("Linux AARCH 64-bit with QCS6490");
|
||||
}
|
||||
await expect(page.getByText("Object Detection")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Upload model", async ({ page }) => {
|
||||
const testsDir = process.env.TESTS_DIR;
|
||||
if (!testsDir) {
|
||||
throw new Error("TESTS_DIR is not set");
|
||||
}
|
||||
|
||||
await page.getByRole("button", { name: "Import Model" }).click();
|
||||
await page.getByRole("textbox", { name: "Labels" }).fill(fakeLabels);
|
||||
await page.getByRole("spinbutton", { name: "Width" }).fill("640");
|
||||
await page.getByRole("spinbutton", { name: "Height" }).fill("640");
|
||||
await page.getByTestId("import-version-select").click();
|
||||
await page.getByRole("option", { name: "YOLOv8" }).click();
|
||||
|
||||
const modelFile = platform.endsWith("RK3588_64") ? `${fakeModelName}.rknn` : `${fakeModelName}.tflite`;
|
||||
await page
|
||||
.getByRole("button", { name: "Model File Model File" })
|
||||
.setInputFiles(path.join(testsDir, "tests/resources", modelFile));
|
||||
|
||||
await page.getByRole("button", { name: "Import Object Detection Model" }).click();
|
||||
|
||||
await page.goto("/#/settings");
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: fakeModelName });
|
||||
|
||||
await expect(tableRow).toBeVisible();
|
||||
await expect(tableRow).toContainText(fakeLabels);
|
||||
});
|
||||
|
||||
test("Rename model", async ({ page }) => {
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: fakeModelName });
|
||||
|
||||
await tableRow.getByRole("button", { name: "Rename Model" }).click();
|
||||
await page.getByRole("textbox", { name: "New Name New Name" }).fill(newModelName);
|
||||
await page.getByRole("button", { name: "Rename", exact: true }).click();
|
||||
|
||||
await page.reload();
|
||||
|
||||
const renamedRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
await expect(renamedRow).toContainText(fakeLabels);
|
||||
});
|
||||
|
||||
test("Delete model", async ({ page }) => {
|
||||
const tableRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
|
||||
await tableRow.getByRole("button", { name: "Delete Model" }).click();
|
||||
await page.getByRole("button", { name: "Delete model", exact: true }).click();
|
||||
|
||||
await page.reload();
|
||||
const deletedRow = page.getByTestId("model-table").locator("tr", { hasText: newModelName });
|
||||
await expect(deletedRow).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
0
photon-client/tests/resources/FAKE-MODEL.rknn
Normal file
0
photon-client/tests/resources/FAKE-MODEL.rknn
Normal file
0
photon-client/tests/resources/FAKE-MODEL.tflite
Normal file
0
photon-client/tests/resources/FAKE-MODEL.tflite
Normal file
Reference in New Issue
Block a user