feat: implement Playwright service for browser automation
- Add playwright types for Credentials, Session, and LoginResult - Implement PlaywrightService with launchBrowser, login, closeBrowser, and newPage methods - Support browser configuration (headless mode, slowMo, timeout) - Integrate with LoggerService for comprehensive logging - Handle HTTPS errors via ERP configuration Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
17
src/main/models/playwright.types.ts
Normal file
17
src/main/models/playwright.types.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Browser, BrowserContext, Page, Frame } from 'playwright';
|
||||||
|
|
||||||
|
export interface Credentials {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Session {
|
||||||
|
browser: Browser;
|
||||||
|
context: BrowserContext;
|
||||||
|
page: Page;
|
||||||
|
mainFrame: Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResult extends Session {
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
123
src/main/services/playwright.service.ts
Normal file
123
src/main/services/playwright.service.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { chromium, Browser, BrowserContext, Page, Frame } from 'playwright';
|
||||||
|
import { Credentials, Session, LoginResult } from '../models/playwright.types';
|
||||||
|
import { BrowserConfig, ERPConfig } from '../models/config.types';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
|
export class PlaywrightService {
|
||||||
|
private browser?: Browser;
|
||||||
|
private context?: BrowserContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private browserConfig: BrowserConfig,
|
||||||
|
private erpConfig: ERPConfig
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async launchBrowser(): Promise<Browser> {
|
||||||
|
try {
|
||||||
|
LoggerService.info('Launching Chromium browser...');
|
||||||
|
|
||||||
|
this.browser = await chromium.launch({
|
||||||
|
headless: this.browserConfig.headless,
|
||||||
|
slowMo: this.browserConfig.slowMo,
|
||||||
|
timeout: this.browserConfig.timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.context = await this.browser.newContext({
|
||||||
|
ignoreHTTPSErrors: this.erpConfig.ignoreHttpsErrors,
|
||||||
|
});
|
||||||
|
|
||||||
|
LoggerService.info('Browser launched successfully');
|
||||||
|
return this.browser;
|
||||||
|
} catch (error) {
|
||||||
|
LoggerService.error('Failed to launch browser', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(credentials: Credentials): Promise<LoginResult> {
|
||||||
|
if (!this.browser || !this.context) {
|
||||||
|
throw new Error('Browser not launched');
|
||||||
|
}
|
||||||
|
|
||||||
|
let page: Page | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoggerService.info('Attempting ERP login...');
|
||||||
|
|
||||||
|
page = await this.context.newPage();
|
||||||
|
await page.goto(this.erpConfig.baseUrl, {
|
||||||
|
timeout: this.browserConfig.timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for login form
|
||||||
|
await page.waitForSelector('input[name="username"]', {
|
||||||
|
timeout: this.browserConfig.timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fill credentials
|
||||||
|
await page.fill('input[name="username"]', credentials.username);
|
||||||
|
await page.fill('input[name="password"]', credentials.password);
|
||||||
|
|
||||||
|
// Click login button
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
// Wait for navigation
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
|
||||||
|
// Get main frame
|
||||||
|
const mainFrame = page.mainFrame();
|
||||||
|
|
||||||
|
LoggerService.info('Login successful');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
browser: this.browser,
|
||||||
|
context: this.context,
|
||||||
|
page,
|
||||||
|
mainFrame,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
LoggerService.error('Login failed', error);
|
||||||
|
|
||||||
|
// Clean up page on error to prevent resource leak
|
||||||
|
if (page) {
|
||||||
|
try {
|
||||||
|
await page.close();
|
||||||
|
} catch (closeError) {
|
||||||
|
LoggerService.error('Failed to close page after login error', closeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeBrowser(): Promise<void> {
|
||||||
|
// Close context first
|
||||||
|
if (this.context) {
|
||||||
|
try {
|
||||||
|
await this.context.close();
|
||||||
|
LoggerService.info('Browser context closed');
|
||||||
|
} catch (error) {
|
||||||
|
LoggerService.error('Failed to close browser context', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always attempt to close browser, even if context.close() failed
|
||||||
|
if (this.browser) {
|
||||||
|
try {
|
||||||
|
await this.browser.close();
|
||||||
|
LoggerService.info('Browser closed');
|
||||||
|
} catch (error) {
|
||||||
|
LoggerService.error('Failed to close browser', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async newPage(): Promise<Page> {
|
||||||
|
if (!this.context) {
|
||||||
|
throw new Error('Browser context not initialized');
|
||||||
|
}
|
||||||
|
return await this.context.newPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user