mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
test(electron): add Playwright smoke E2E tests for app
Add a new Electron smoke test suite using Playwright to verify the packaged application starts, renders the main window, and exposes expected window properties. The tests launch the built Electron main bundle, locate the primary (non-DevTools) window, wait for DOM load, and assert the title contains "IPTVnator". They also check window visibility and bounds, validate rendered body content, and capture screenshots to dist/test-results/electron-backend-e2e for debugging. Include helpers to resolve project paths, ensure the screenshot directory exists, and robustly find the main application window. Set NODE_ENV and ELECTRON_IS_DEV to ensure the app loads built artifacts.
This commit is contained in:
1 parent
c7fff52bc9
commit
e8a80fdb60
6 files changed
+301
-1
No files matched your search
@@ -0,0 +1,12 @@
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
playwright.configs['flat/recommended'],
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.js'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Playwright configuration for Electron e2e tests.
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './src',
|
||||
/* Match .e2e.ts files */
|
||||
testMatch: '**/*.e2e.ts',
|
||||
/* Run tests sequentially since we're testing an Electron app */
|
||||
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,
|
||||
/* Single worker since we're testing an Electron app */
|
||||
workers: 1,
|
||||
/* Reporter to use */
|
||||
reporter: [
|
||||
['list'],
|
||||
['html', { outputFolder: '../../dist/playwright-report/electron-backend-e2e' }],
|
||||
],
|
||||
/* Shared settings for all the projects below */
|
||||
use: {
|
||||
/* Collect trace when retrying the failed test */
|
||||
trace: 'on-first-retry',
|
||||
/* Screenshots on failure */
|
||||
screenshot: 'on',
|
||||
/* Video on failure */
|
||||
video: 'on-first-retry',
|
||||
},
|
||||
/* Output folder for test artifacts */
|
||||
outputDir: '../../dist/test-results/electron-backend-e2e',
|
||||
/* Timeout for each test */
|
||||
timeout: 60000,
|
||||
/* Timeout for expect() assertions */
|
||||
expect: {
|
||||
timeout: 10000,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'electron',
|
||||
use: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "electron-backend-e2e",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "apps/electron-backend-e2e/src",
|
||||
"implicitDependencies": ["electron-backend"],
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"dependsOn": ["electron-backend:build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import {
|
||||
_electron as electron,
|
||||
ElectronApplication,
|
||||
expect,
|
||||
Page,
|
||||
test,
|
||||
} from '@playwright/test';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
/**
|
||||
* Smoke test for the Electron application.
|
||||
* Verifies that the app starts correctly and renders the main window.
|
||||
*/
|
||||
|
||||
let electronApp: ElectronApplication;
|
||||
let mainWindow: Page;
|
||||
|
||||
// Workspace root (where nx.json is located)
|
||||
const workspaceRoot = resolve(__dirname, '../../..');
|
||||
|
||||
// Path to the packaged Electron app main.js file
|
||||
const electronMainPath = join(
|
||||
workspaceRoot,
|
||||
'dist/apps/electron-backend/main.js'
|
||||
);
|
||||
|
||||
// Path for screenshots
|
||||
const screenshotDir = join(
|
||||
workspaceRoot,
|
||||
'dist/test-results/electron-backend-e2e'
|
||||
);
|
||||
|
||||
// Ensure screenshot directory exists
|
||||
if (!existsSync(screenshotDir)) {
|
||||
mkdirSync(screenshotDir, { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to find the main application window (not DevTools)
|
||||
*/
|
||||
async function findMainWindow(app: ElectronApplication): Promise<Page> {
|
||||
// Wait for windows to be available
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
const windows = app.windows();
|
||||
console.log(`Found ${windows.length} window(s)`);
|
||||
|
||||
for (const window of windows) {
|
||||
const title = await window.title();
|
||||
console.log(`Window title: "${title}"`);
|
||||
// Skip DevTools windows
|
||||
if (!title.includes('DevTools')) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
// If no non-DevTools window found, wait for one
|
||||
console.log('Waiting for main window...');
|
||||
return await app.firstWindow();
|
||||
}
|
||||
|
||||
test.describe('Electron App Smoke Test', () => {
|
||||
test.beforeAll(async () => {
|
||||
console.log(`Launching Electron app from: ${electronMainPath}`);
|
||||
console.log(`Platform: ${process.platform}`);
|
||||
console.log(`CI: ${process.env['CI']}`);
|
||||
|
||||
// Check if web build exists
|
||||
const webIndexPath = join(workspaceRoot, 'dist/apps/web/index.html');
|
||||
console.log(`Web index path: ${webIndexPath}`);
|
||||
console.log(`Web index exists: ${existsSync(webIndexPath)}`);
|
||||
|
||||
// Launch the Electron app
|
||||
// Set ELECTRON_IS_DEV=0 to load from built files instead of dev server
|
||||
// On Linux CI, we need to disable sandbox due to permission issues
|
||||
const args = [electronMainPath];
|
||||
if (process.platform === 'linux' && process.env['CI']) {
|
||||
args.unshift('--no-sandbox', '--disable-gpu');
|
||||
}
|
||||
|
||||
electronApp = await electron.launch({
|
||||
args,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
ELECTRON_IS_DEV: '0',
|
||||
},
|
||||
});
|
||||
|
||||
// Find the main application window (not DevTools)
|
||||
mainWindow = await findMainWindow(electronApp);
|
||||
|
||||
// Wait for the window to be fully loaded
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Debug: Log the current URL and page content
|
||||
const url = mainWindow.url();
|
||||
console.log(`Page URL: ${url}`);
|
||||
|
||||
// Wait a moment and check the HTML content for debugging
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
const htmlContent = await mainWindow.content();
|
||||
console.log(`HTML content length: ${htmlContent.length}`);
|
||||
console.log(`HTML snippet: ${htmlContent.substring(0, 500)}`);
|
||||
|
||||
// Check for any errors in the page
|
||||
const errors: string[] = [];
|
||||
mainWindow.on('pageerror', (error) => {
|
||||
errors.push(error.message);
|
||||
console.log(`Page error: ${error.message}`);
|
||||
});
|
||||
|
||||
// Wait for Angular to bootstrap and render (app-root should have content)
|
||||
try {
|
||||
await mainWindow.waitForSelector('app-root', { timeout: 30000 });
|
||||
} catch (e) {
|
||||
console.log(`Failed to find app-root. Errors: ${errors.join(', ')}`);
|
||||
console.log(`Final HTML: ${await mainWindow.content()}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Wait a bit more for Angular to fully initialize and set the title
|
||||
await mainWindow.waitForFunction(
|
||||
() => document.title.length > 0,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
console.log('Electron app launched successfully');
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Close the Electron app
|
||||
if (electronApp) {
|
||||
await electronApp.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('app should start and display main window', async () => {
|
||||
// Check that we have a window
|
||||
expect(mainWindow).toBeTruthy();
|
||||
|
||||
// Take a screenshot of the main window
|
||||
await mainWindow.screenshot({
|
||||
path: join(screenshotDir, 'smoke-test-screenshot.png'),
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
// Verify the window title contains expected text
|
||||
const title = await mainWindow.title();
|
||||
console.log(`Window title: ${title}`);
|
||||
|
||||
// IPTVnator should be in the title
|
||||
expect(title).toContain('IPTVnator');
|
||||
});
|
||||
|
||||
test('app should have expected window properties', async () => {
|
||||
// Check that the window is visible
|
||||
const isVisible = await electronApp.evaluate(({ BrowserWindow }) => {
|
||||
const mainWindow = BrowserWindow.getAllWindows()[0];
|
||||
return mainWindow ? mainWindow.isVisible() : false;
|
||||
});
|
||||
expect(isVisible).toBe(true);
|
||||
|
||||
// Check window dimensions are reasonable
|
||||
const bounds = await electronApp.evaluate(({ BrowserWindow }) => {
|
||||
const mainWindow = BrowserWindow.getAllWindows()[0];
|
||||
return mainWindow ? mainWindow.getBounds() : null;
|
||||
});
|
||||
expect(bounds).not.toBeNull();
|
||||
expect(bounds?.width).toBeGreaterThan(800);
|
||||
expect(bounds?.height).toBeGreaterThan(600);
|
||||
|
||||
// Take another screenshot after property checks
|
||||
await mainWindow.screenshot({
|
||||
path: join(screenshotDir, 'window-properties-screenshot.png'),
|
||||
});
|
||||
});
|
||||
|
||||
test('app should render main content', async () => {
|
||||
// Wait for app-root to have actual content (not just the element)
|
||||
await mainWindow.waitForFunction(
|
||||
() => {
|
||||
const appRoot = document.querySelector('app-root');
|
||||
return appRoot && appRoot.innerHTML.trim().length > 0;
|
||||
},
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
// Check that the body has content
|
||||
const bodyContent = await mainWindow.locator('body').innerHTML();
|
||||
expect(bodyContent.length).toBeGreaterThan(0);
|
||||
|
||||
// Take a final screenshot showing the rendered content
|
||||
await mainWindow.screenshot({
|
||||
path: join(screenshotDir, 'rendered-content-screenshot.png'),
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
console.log(
|
||||
'Smoke test completed successfully - app is starting and rendering'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"sourceMap": false,
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"playwright.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.e2e.ts"
|
||||
]
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export default class App {
|
||||
|
||||
private static loadMainWindow() {
|
||||
// load the index.html of the app.
|
||||
if (!App.application.isPackaged) {
|
||||
if (App.isDevelopmentMode()) {
|
||||
App.mainWindow.loadURL(`http://localhost:${rendererAppPort}`);
|
||||
App.mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user