Migrate e2e testing from Cypress to Playwright (#2746)

* refactor: migrate e2e testing from Cypress to Playwright
This commit is contained in:
luzhuang
2025-07-08 14:38:49 +08:00
committed by GitHub
parent 95135f4f23
commit c50e4f2a18
19 changed files with 515 additions and 955 deletions

View File

@@ -66,7 +66,7 @@ jobs:
- name: Install
run: pnpm install
- name: Install Playwright Browsers
run: npx playwright install --with-deps
run: npx playwright install --with-deps chromium
- name: Build
run: npm run build
- name: Test
@@ -79,11 +79,12 @@ jobs:
flags: unittests
e2e:
runs-on: macos-latest
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- uses: actions/checkout@v3
@@ -95,29 +96,78 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Run Cypress Tests
uses: cypress-io/github-action@v5
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Get installed Playwright version
id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV
- name: Cache Playwright browsers
uses: actions/cache@v3
id: playwright-cache
with:
build: npm run build
start: npm run e2e:case
wait-on: "http://localhost:5175"
wait-on-timeout: 120
browser: chrome
- name: Upload Diff
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
- name: Install Playwright Browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
timeout-minutes: 15
run: |
echo "🔧 Installing Playwright chromium browser..."
npx playwright install chromium
npx playwright install-deps chromium
echo "✅ Playwright installation completed"
- name: Build project
run: npm run build
- name: Pull baseline images
run: git lfs pull
- name: Verify browser installation
run: |
echo "🔍 Verifying browser installation..."
ls -la ~/.cache/ms-playwright/ || echo "Browser cache directory not found"
find ~/.cache/ms-playwright -name "*headless_shell*" || echo "Headless shell not found"
- name: Run Playwright Tests
run: |
echo "🧪 Starting e2e visual regression tests..."
echo "📊 Test environment: ${{ runner.os }} - Node ${{ matrix.node-version }}"
start_time=$(date +%s)
npx playwright test --shard=${{ matrix.shard }} --reporter=list,github
exit_code=$?
end_time=$(date +%s)
duration=$((end_time - start_time))
if [ $exit_code -eq 0 ]; then
echo "✅ E2E tests completed successfully in ${duration}s!"
else
echo "❌ E2E tests failed in ${duration}s with exit code $exit_code"
exit $exit_code
fi
env:
CI: true
PLAYWRIGHT_FORCE_TTY: 1
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-diff
path: e2e/diff/
- name: Upload Origin
name: playwright-test-results-${{ strategy.job-index }}-${{ github.run_number }}
path: |
e2e/test-results/
!e2e/test-results/**/*.webm
retention-days: 7
- name: Upload diff images
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-origin
path: e2e/fixtures/originImage
- name: Upload Screenshots
name: playwright-diffs-${{ strategy.job-index }}-${{ github.run_number }}
path: e2e/test-results/diffs/
retention-days: 14
- name: Upload HTML report
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-screenshots
path: e2e/downloads/
name: playwright-report-${{ strategy.job-index }}-${{ github.run_number }}
path: playwright-report/
retention-days: 7

7
.gitignore vendored
View File

@@ -31,6 +31,13 @@ e2e/screenshots/*
e2e/downloads/*
e2e/diff/*
e2e/.dev/mpa
e2e/test-results/*
playwright-report/
# Claude Code files
.claude/
CLAUDE.md
.husky/post-checkout
.husky/post-commit
.husky/pre-push

View File

@@ -1,78 +0,0 @@
import { defineConfig } from "cypress";
import { compare } from "odiff-bin";
const path = require("path");
const fs = require("fs-extra");
const downloadDirectory = path.join(__dirname, "e2e/downloads");
let isRunningInCommandLine = false;
export default defineConfig({
e2e: {
viewportWidth: 1200,
viewportHeight: 800,
baseUrl: "http://localhost:5175",
defaultCommandTimeout: 60000,
fileServerFolder: "e2e",
supportFile: "e2e/support/e2e.ts",
fixturesFolder: "e2e/fixtures",
screenshotsFolder: "e2e/screenshots",
videosFolder: "e2e/videos",
specPattern: "e2e/tests/*.cy.ts",
downloadsFolder: "e2e/downloads",
video: false,
setupNodeEvents(on, config) {
// implement node event listeners here
on("before:browser:launch", (browser, launchOptions) => {
console.log("launching browser %s is headless? %s", browser.name, browser.isHeadless);
// supply the absolute path to an unpacked extension's folder
// NOTE: extensions cannot be loaded in headless Chrome
if (fs.existsSync("e2e/diff")) {
fs.rmdirSync("e2e/diff", { recursive: true });
}
if (browser.name === "chrome") {
launchOptions.preferences.default["download"] = {
default_directory: downloadDirectory
};
}
if (browser.isHeadless) {
isRunningInCommandLine = true;
}
launchOptions.args.push("--force-device-scale-factor=1");
return launchOptions;
}),
on("task", {
async compare({ fileName, options }) {
const baseFolder = "e2e/fixtures/originImage/";
const newFolder = path.join("e2e/downloads");
const diffFolder = path.join("e2e/diff", options.specFolder);
if (!fs.existsSync(diffFolder)) {
fs.mkdirSync(diffFolder, { recursive: true });
}
const baseImage = path.join(baseFolder, fileName);
const newImage = path.join(newFolder, fileName);
const diffImage = path.join(diffFolder, fileName);
console.log("comparing base image %s to the new image %s", baseImage, newImage);
if (options) {
console.log("odiff options %o", options);
}
const started = +new Date();
const result = await compare(baseImage, newImage, diffImage, options);
const finished = +new Date();
const elapsed = finished - started;
console.log("odiff took %dms", elapsed);
//@ts-ignore
if (result.match === false && result.diffPercentage <= 0.1) {
//@ts-ignore
result.match = true;
}
console.log(result);
return result;
}
});
}
},
chromeWebSecurity: false
});

View File

@@ -45,7 +45,8 @@ module.exports = {
server: {
open: true,
host: "0.0.0.0",
port: 5175
port: 5175,
strictPort: true
},
resolve: {
dedupe: ["@galacean/engine"]

View File

@@ -1,43 +1,105 @@
### Note: Require install git-lfs
We use [git-lfs](https://git-lfs.com/) (Install by official website) to manage baseline images for e2e tests, so it's necessary to install it, ignore if already installed.
### 1. Create a case page in the e2e/case directory
You can refer to e2e/case/animator-play.ts.
### 2. Configure your e2e test in e2e/config.ts
The threshold is color difference threshold (from 0 to 1). Less more precise.
### 3. Debug your test cases:
#### Launch the Case page:
# E2E Testing Guide
```
npm run e2e:case
```
## Prerequisites
After successfully launching the case page, run:
```
### Git LFS
We use [git-lfs](https://git-lfs.com/) to manage baseline images for e2e tests. Install it if you haven't already:
```bash
git lfs install
git lfs pull
```
Pull image from github, then run
## Quick Start
### Run all e2e tests
```bash
npm run e2e
```
### Debug tests interactively
```bash
npm run e2e:debug
```
Open the Cypress client for debugging.
Cypress will capture screenshots of your case pages.
Review the screenshots in e2e/downloads folder, store them in the e2e/fixtures/originImage directory if there are no issues, then rerun the test cases. If the test cases pass, the debugging is complete.
Both commands will automatically:
- Install required browsers (Chromium)
- Start the test server
- Run visual regression tests with odiff comparison
## Project Structure
### 4. Run the complete e2e tests:
```
npm run e2e
e2e/
├── case/ # Test case implementations
├── config.ts # Test configuration
├── fixtures/
│ └── originImage/ # Baseline images (managed by git-lfs)
├── downloads/ # Generated screenshots
├── tests/ # Playwright test files
└── utils/ # Helper utilities
```
Note: The e2e testing framework for this project is Cypress. For detailed usage instructions, please refer to https://www.cypress.io/.
### Add new e2e case
1. modify `config.ts` based on the new test case.
2. run `npm run e2e:debug`
the new image of test case for comparison will be present under directory `e2e/downloads`, you need to copy it into directory `e2e/fixtures/originImage`.
## Adding New Test Cases
### 1. Create a test case file
Create your test implementation in `e2e/case/`, following existing patterns:
```typescript
// e2e/case/my-new-test.ts
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
// Your test implementation
updateForE2E(engine);
initScreenshot(engine, camera);
});
```
### 2. Add configuration
Add your test to `e2e/config.ts`:
```typescript
MyCategory: {
myNewTest: {
category: "MyCategory",
caseFileName: "my-new-test",
threshold: 0.1 // 0.01 for strict tests, 0.1 for normal tests
}
}
```
### 3. Generate baseline image
Run in debug mode to generate the initial screenshot:
```bash
npm run e2e:debug
```
Copy the generated image from `e2e/downloads/` to `e2e/fixtures/originImage/` and commit it with git-lfs.
## Threshold Guidelines
- **0.01**: Strict comparison for pixel-perfect tests (e.g., FXAA, transparency)
- **0.1**: Normal comparison for most 3D rendering tests
- Adjust based on rendering stability and requirements
## Troubleshooting
### Browser installation issues
Manually install browsers:
```bash
npm run e2e:install
```
### Missing baseline images
Pull from git-lfs:
```bash
git lfs pull
```
### Server startup issues
Manually start the test server:
```bash
npm run e2e:case
```
## Framework Details
This project uses [Playwright](https://playwright.dev/) with [odiff](https://github.com/dmtrKovalenko/odiff) for visual regression testing. All tests run in Chromium for consistency.

View File

@@ -92,17 +92,25 @@ export function initScreenshot(
const imageName = `${category}_${caseFileName}.jpg`;
a.href = url;
a.download = imageName;
a.id = "screenshot";
a.dataset.testid = "screenshot";
a.textContent = "Download Screenshot";
a.style.cssText = `
position: fixed;
top: 10px;
left: 10px;
z-index: 9999;
background: #007bff;
color: white;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
font-family: Arial, sans-serif;
font-size: 14px;
border: none;
cursor: pointer;
`;
document.body.appendChild(a);
a.addEventListener("click", () => {
if (a.parentElement) {
a.parentElement.removeChild(a);
}
});
// window.URL.revokeObjectURL(url);
// revert
callbacks.forEach((cb) => cb());
!isPaused && engine.resume();

View File

@@ -237,7 +237,7 @@ export const E2E_CONFIG = {
caseFileName: "particleRenderer-dream",
threshold: 0.1
},
particleFire: {
particleFire: {
category: "Particle",
caseFileName: "particleRenderer-fire",
threshold: 0.1

67
e2e/global-setup.ts Normal file
View File

@@ -0,0 +1,67 @@
import * as fs from "fs-extra";
import * as path from "path";
// Wait for server to be ready
async function waitForServer(url: string, timeout: number = 120000): Promise<void> {
const startTime = Date.now();
console.log(`⏳ Waiting for server at ${url}...`);
while (Date.now() - startTime < timeout) {
try {
const response = await fetch(url);
if (response.ok) {
console.log(`✅ Server is ready at ${url}`);
return;
}
} catch (error) {
// Server not ready yet, continue waiting
}
// Wait 1 second before next attempt
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error(`❌ Server at ${url} did not start within ${timeout}ms`);
}
export default async function globalSetup() {
console.log("🚀 Galacean Engine E2E Test Setup");
console.log("📁 Cleaning downloads directory...");
// Clean downloads directory before tests
const downloadsPath = path.join(process.cwd(), "e2e/downloads");
if (fs.existsSync(downloadsPath)) {
const files = fs.readdirSync(downloadsPath);
fs.emptyDirSync(downloadsPath);
console.log(` ✅ Cleaned ${files.length} files from e2e/downloads`);
} else {
console.log(" Downloads directory is empty");
}
// Count test cases from config
let testCount = 0;
try {
const configPath = path.join(process.cwd(), "e2e/config.ts");
if (fs.existsSync(configPath)) {
const { E2E_CONFIG } = require(configPath);
testCount = Object.values(E2E_CONFIG).reduce((total: number, category: any) => {
return total + Object.keys(category).length;
}, 0) as number;
console.log(`🧪 Found ${testCount} test cases`);
}
} catch (error) {
console.log("⚠️ Could not read test configuration");
}
// Check baseline images
const baselineDir = path.join(process.cwd(), "e2e/fixtures/originImage");
if (fs.existsSync(baselineDir)) {
const baselineFiles = fs.readdirSync(baselineDir).filter((f) => f.endsWith(".jpg"));
console.log(`📸 Found ${baselineFiles.length} baseline images`);
}
// Wait for server to be ready
await waitForServer("http://localhost:5175");
console.log("🎬 Ready to run visual regression tests!\n");
}

View File

@@ -1,73 +0,0 @@
import { recurse } from "cypress-recurse";
import * as path from "path";
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
declare global {
namespace Cypress {
interface Chainable {
screenshotWithThreshold(category: string, name: string, threshold?: number): Chainable<Element>;
}
}
}
Cypress.Commands.add("screenshotWithThreshold", (category, name, threshold = 0) => {
const downloadsFolder = Cypress.config("downloadsFolder");
cy.visit(`/mpa/${name}.html?category=${category}&case=${name}`);
const imageName = `${category}_${name}.jpg`;
const filePath = path.join(downloadsFolder, imageName);
cy.get("#screenshot", { timeout: 60000 })
.click({ force: true })
.then(() => {
return new Promise((resolve) => {
cy.log(`Reading file ${filePath}`);
resolve(
recurse(
() => {
return cy.readFile(filePath).then(() => {
cy.log(`Comparing ${imageName} with threshold ${threshold}`);
return cy.task("compare", {
fileName: imageName,
options: {
specFolder: Cypress.spec.name,
threshold,
antialiasing: true
}
});
});
},
({ match }) => match,
{
limit: 2
}
)
);
});
});
});

View File

@@ -1,20 +0,0 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -1,14 +0,0 @@
import { E2E_CONFIG } from "../config";
for (let category in E2E_CONFIG) {
const config = E2E_CONFIG[category];
describe(category, () => {
for (const caseName in config) {
it(caseName, () => {
const { category, caseFileName, threshold } = config[caseName];
cy.screenshotWithThreshold(category, caseFileName, threshold);
});
}
});
}

27
e2e/tests/index.spec.ts Normal file
View File

@@ -0,0 +1,27 @@
import { test } from "@playwright/test";
import { E2E_CONFIG } from "../config";
import { screenshotWithThreshold } from "../utils/screenshot";
import type { CategoryConfig, TestCaseConfig } from "../types/test-config";
/**
* Create test cases for a given category
*/
function createTestsForCategory(categoryName: string, categoryConfig: CategoryConfig) {
test.describe(categoryName, () => {
Object.entries(categoryConfig).forEach(([caseName, config]: [string, TestCaseConfig]) => {
test(caseName, async ({ page }) => {
const { category, caseFileName, threshold } = config;
await screenshotWithThreshold(page, {
category,
name: caseFileName,
threshold
});
});
});
});
}
// Generate test suites for all categories
Object.entries(E2E_CONFIG).forEach(([categoryName, categoryConfig]) => {
createTestsForCategory(categoryName, categoryConfig);
});

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
"types": ["@playwright/test", "node"]
},
"include": ["**/*.ts"]
}

85
e2e/utils/screenshot.ts Normal file
View File

@@ -0,0 +1,85 @@
import { Page } from "@playwright/test";
import { compare } from "odiff-bin";
import * as path from "path";
export interface ScreenshotOptions {
category: string;
name: string;
threshold?: number;
}
export async function screenshotWithThreshold(page: Page, options: ScreenshotOptions) {
const { category, name, threshold = 0.1 } = options;
const imageName = `${category}_${name}.jpg`;
const testId = `${category}_${name}`;
const startTime = Date.now();
console.log(`📸 [${testId}] Starting test`);
const testUrl = `/mpa/${name}.html`;
console.log(`🌐 [${testId}] Navigating to ${testUrl}`);
await page.goto(testUrl);
console.log(`⏳ [${testId}] Waiting for DOM content loaded...`);
await page.waitForLoadState("domcontentloaded");
const pageReadyTime = Date.now();
console.log(`📄 [${testId}] Page ready (${pageReadyTime - startTime}ms)`);
console.log(`🔍 [${testId}] Looking for screenshot button...`);
// 监听下载事件
const downloadPromise = page.waitForEvent("download");
console.log(`📡 [${testId}] Download listener set up`);
console.log(`⏰ [${testId}] Waiting for screenshot button to be visible (timeout: 180s)...`);
let pageRenderedTime;
try {
// 等待 screenshot 按钮可见
await page.getByTestId("screenshot").waitFor({ timeout: 180000 });
pageRenderedTime = Date.now();
console.log(`✅ [${testId}] Screenshot button visible (${pageRenderedTime - pageReadyTime}ms)`);
} catch (error) {
console.log(`❌ [${testId}] Screenshot button not visible after 180s`);
console.log(`🔍 [${testId}] Page content: ${await page.content()}`);
throw error;
}
console.log(`👆 [${testId}] Clicking screenshot button...`);
// 点击下载按钮
await page.getByTestId("screenshot").click();
console.log(`✅ [${testId}] Screenshot button clicked`);
console.log(`⬇️ [${testId}] Waiting for download to start...`);
// 等待下载完成
const download = await downloadPromise;
console.log(`📦 [${testId}] Download received`);
console.log(`💾 [${testId}] Saving download...`);
const downloadPath = path.join(process.cwd(), "e2e/downloads", imageName);
// 保存下载的文件
await download.saveAs(downloadPath);
console.log(`📥 [${testId}] Downloaded (${Date.now() - pageRenderedTime}ms)`);
// Compare with baseline
const baseImagePath = path.join(process.cwd(), "e2e/fixtures/originImage", imageName);
const diffImagePath = path.join(process.cwd(), "e2e/test-results/diffs", imageName);
const result = await compare(baseImagePath, downloadPath, diffImagePath, {
threshold: threshold * 100,
antialiasing: true
});
if (!result.match) {
const diffPercentage = "diffPercentage" in result ? result.diffPercentage : "unknown";
console.log(`❌ [${testId}] Visual regression: ${diffPercentage}% (${Date.now() - startTime}ms)`);
throw new Error(
`Visual regression detected for ${imageName}. ` +
`Difference: ${diffPercentage}%, threshold: ${threshold * 100}%. ` +
`Diff saved to: ${diffImagePath}`
);
}
console.log(`✅ [${testId}] Test passed (${Date.now() - startTime}ms total)`);
return result;
}

View File

@@ -5,9 +5,9 @@
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"pretest": "pnpm exec playwright install",
"pretest": "vitest --version && playwright install --with-deps chromium",
"test": "vitest",
"coverage": "vitest --coverage",
"coverage": "cross-env HEADLESS=true vitest --coverage",
"build": "npm run b:module && npm run b:types",
"lint": "eslint packages/*/src --ext .ts",
"watch": "cross-env NODE_ENV=release BUILD_TYPE=MODULE rollup -cw -m inline",
@@ -19,14 +19,17 @@
"b:all": "cross-env NODE_ENV=release npm run b:types && cross-env BUILD_TYPE=ALL NODE_ENV=release rollup -c",
"clean": "pnpm -r exec rm -rf dist && pnpm -r exec rm -rf types",
"e2e:case": "pnpm -C ./e2e run case",
"e2e": "cypress run --browser chrome --headless",
"e2e:debug": "cypress open",
"pree2e": "playwright install --with-deps chromium",
"e2e": "playwright test",
"pree2e:debug": "playwright install --with-deps chromium",
"e2e:debug": "playwright test --ui",
"prepare": "husky install",
"release": "bumpp -r"
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@playwright/test": "^1.53.1",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-inject": "^4.0.2",
"@rollup/plugin-node-resolve": "^11.0.1",
@@ -41,8 +44,6 @@
"@vitest/coverage-v8": "2.1.3",
"bumpp": "^9.5.2",
"cross-env": "^5.2.0",
"cypress": "^12.17.1",
"cypress-recurse": "^1.23.0",
"electron": "^13",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
@@ -52,7 +53,6 @@
"lint-staged": "^10.5.3",
"nyc": "^15.1.0",
"odiff-bin": "^2.5.0",
"playwright": "^1.48.1",
"prettier": "^3.0.0",
"rollup": "^2.36.1",
"rollup-plugin-glslify": "^1.2.0",

53
playwright.config.ts Normal file
View File

@@ -0,0 +1,53 @@
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
globalSetup: "./e2e/global-setup.ts",
testDir: "./e2e/tests",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: process.env.CI ? [["github"], ["list"], ["html", { open: "never" }]] : "html",
timeout: process.env.CI ? 180000 : 120000,
use: {
baseURL: "http://localhost:5175",
trace: process.env.CI ? "on-first-retry" : "on-first-retry",
video: process.env.CI ? "off" : "retain-on-failure",
screenshot: "only-on-failure",
actionTimeout: process.env.CI ? 180000 : 30000,
navigationTimeout: process.env.CI ? 180000 : 60000
},
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1200, height: 800 },
launchOptions: {
args: process.env.CI
? [
"--use-gl=angle",
"--use-angle=swiftshader",
"--enable-webgl",
"--ignore-gpu-blocklist",
"--disable-gpu-sandbox",
"--disable-software-rasterizer",
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-web-security",
"--disable-features=VizDisplayCompositor"
]
: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"]
}
}
}
],
webServer: {
command: "npm run e2e:case",
timeout: 120 * 1000,
stdout: "pipe",
stderr: "pipe"
},
outputDir: "e2e/test-results"
});

749
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -69,36 +69,37 @@ describe("webgl engine test", () => {
});
it("engine device lost", async () => {
const canvas = document.createElement("canvas");
const engine = await WebGLEngine.create({ canvas });
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();
// init camera
const cameraEntity = rootEntity.createChild("camera");
const camera = cameraEntity.addComponent(Camera);
const engine = await WebGLEngine.create({ canvas: document.createElement("canvas") });
engine.sceneManager.activeScene.createRootEntity().createChild("camera").addComponent(Camera);
engine.run();
const opLost = vi.fn(() => {
const onLost = vi.fn(() => {
console.log("On device lost.");
});
const onRestored = vi.fn(() => {
console.log("On device restored.");
});
engine.on("devicelost", opLost);
engine.on("devicelost", onLost);
engine.on("devicerestored", onRestored);
engine.forceLoseDevice();
setTimeout(() => {
expect(opLost).toHaveBeenCalledTimes(1);
}, 100);
const originalOnError = window.onerror;
let error: Error | null = null;
window.onerror = (msg, src, line, col, err) => (error = err || new Error(String(msg)));
try {
engine.forceLoseDevice();
await new Promise((r) => setTimeout(r, 100));
expect(onLost).toHaveBeenCalledTimes(1);
setTimeout(() => {
engine.forceRestoreDevice();
}, 1000);
await new Promise((r) => setTimeout(r, 100));
expect(onRestored).toHaveBeenCalledTimes(1);
if (error) throw error;
} finally {
window.onerror = originalOnError;
engine.destroy();
}
});
});
// npx cross-env TS_NODE_PROJECT=tsconfig.tests.json nyc --reporter=lcov floss -p tests/src/*.test.ts -r ts-node/register
// npx cross-env TS_NODE_PROJECT=tsconfig.tests.json nyc --reporter=lcov floss --path tests -r ts-node/register

View File

@@ -20,7 +20,10 @@ export default defineProject({
name: "chromium",
providerOptions: {
launch: {
args: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"]
args:
process.env.HEADLESS === "true"
? ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle", "--headless"]
: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"]
}
}
}