|
@@ -0,0 +1,146 @@
|
|
|
|
|
+const { chromium } = require('@playwright/test');
|
|
|
|
|
+
|
|
|
|
|
+class CollaborativeActions {
|
|
|
|
|
+ constructor(user, headless = false, userIndex = 0) {
|
|
|
|
|
+ this.user = user;
|
|
|
|
|
+ this.headless = headless;
|
|
|
|
|
+ this.userIndex = userIndex;
|
|
|
|
|
+ this.browser = null;
|
|
|
|
|
+ this.context = null;
|
|
|
|
|
+ this.page = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async initialize() {
|
|
|
|
|
+ // Calculate window position and size for side-by-side layout
|
|
|
|
|
+ const screenWidth = 1520;
|
|
|
|
|
+ const screenHeight = 1080;
|
|
|
|
|
+ const windowWidth = Math.floor(screenWidth / 2); // 1280px per window
|
|
|
|
|
+ const windowHeight = screenHeight - 150; // Leave space for dock/menu bar (1450px)
|
|
|
|
|
+ const windowX = this.userIndex * windowWidth;
|
|
|
|
|
+ const windowY = 0; // Small offset from top
|
|
|
|
|
+
|
|
|
|
|
+ this.browser = await chromium.launch({
|
|
|
|
|
+ headless: this.headless,
|
|
|
|
|
+ devtools: true, // Открывает DevTools и предотвращает закрытие окна при отладке
|
|
|
|
|
+ args: [
|
|
|
|
|
+ '--no-sandbox',
|
|
|
|
|
+ '--disable-setuid-sandbox',
|
|
|
|
|
+ `--window-size=${ windowWidth },${ windowHeight }`,
|
|
|
|
|
+ `--window-position=${ windowX },${ windowY }`
|
|
|
|
|
+ ]
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.context = await this.browser.newContext({
|
|
|
|
|
+ viewport: { width: windowWidth, height: windowHeight - 100 }, // Account for browser chrome
|
|
|
|
|
+ deviceScaleFactor: 1 // Ensure 1:1 pixel ratio
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.page = await this.context.newPage();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async login(loginUrl = 'http://localhost:9080/debug.html#XKJ7L2') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.page.goto(loginUrl);
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for login input and fill it using the specified selector
|
|
|
|
|
+ await this.page.waitForSelector('.signin.control-group .row:first-child input', { timeout: 10000 });
|
|
|
|
|
+ await this.page.fill('.signin.control-group .row:first-child input', this.user.login);
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for password input and fill it using the specified selector
|
|
|
|
|
+ await this.page.waitForSelector('.signin.control-group .row:last-child input', { timeout: 10000 });
|
|
|
|
|
+ await this.page.fill('.signin.control-group .row:last-child input', this.user.password);
|
|
|
|
|
+
|
|
|
|
|
+ // Submit the form (assuming there's a submit button or Enter key works)
|
|
|
|
|
+ await this.page.keyboard.press('Enter');
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for login to complete
|
|
|
|
|
+ await this.page.waitForTimeout(3000);
|
|
|
|
|
+ } catch(error) {
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async openDocument(documentUrl) {
|
|
|
|
|
+ await this.page.goto(documentUrl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async typeText(text, delay = 100) {
|
|
|
|
|
+ await this.page.keyboard.type(text, { delay });
|
|
|
|
|
+ await this.page.waitForTimeout(1000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async simulateTyping(duration = 5000) {
|
|
|
|
|
+ const words = ['Hello', 'world', 'this', 'is', 'a', 'test', 'of', 'collaborative', 'editing'];
|
|
|
|
|
+ const endTime = Date.now() + duration;
|
|
|
|
|
+
|
|
|
|
|
+ while(Date.now() < endTime) {
|
|
|
|
|
+ const randomWord = words[Math.floor(Math.random() * words.length)];
|
|
|
|
|
+ await this.typeText(randomWord + ' ', 200);
|
|
|
|
|
+ await this.page.waitForTimeout(Math.random() * 2000 + 500);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async takeScreenshot(filename) {
|
|
|
|
|
+ const screenshotPath = `screenshots/${ this.user.login }_${ filename }_${ Date.now() }.png`;
|
|
|
|
|
+ await this.page.screenshot({ path: screenshotPath });
|
|
|
|
|
+ return screenshotPath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async executeUserActions() {
|
|
|
|
|
+ if(!this.user.actions || this.user.actions.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(let i = 0; i < this.user.actions.length; i++) {
|
|
|
|
|
+ const action = this.user.actions[i];
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Execute the function with action parameters passed through context
|
|
|
|
|
+ await this.page.evaluate(this.selectEditorRange, action);
|
|
|
|
|
+
|
|
|
|
|
+ // Small delay after selection
|
|
|
|
|
+ await this.page.waitForTimeout(200);
|
|
|
|
|
+
|
|
|
|
|
+ // If action contains text, type it character by character
|
|
|
|
|
+ if(action.text && action.text.length > 0) {
|
|
|
|
|
+ const keyInterval = action.keyInterval || 300; // Use custom interval or default 300ms
|
|
|
|
|
+ await this.typeTextCharByChar(action.text, keyInterval);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Small delay between actions
|
|
|
|
|
+ await this.page.waitForTimeout(300);
|
|
|
|
|
+
|
|
|
|
|
+ } catch(error) {
|
|
|
|
|
+ // Silent error handling
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Type text character by character with minimal delay
|
|
|
|
|
+ async typeTextCharByChar(text, delay) {
|
|
|
|
|
+ for(let i = 0; i < text.length; i++) {
|
|
|
|
|
+ await this.page.keyboard.type(text[i]);
|
|
|
|
|
+ if(delay > 0)
|
|
|
|
|
+ await this.page.waitForTimeout(delay);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Proper JavaScript function to be executed in browser context
|
|
|
|
|
+ selectEditorRange(actionData) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const editor = window.MainFrame.getItems().last().getEditor();
|
|
|
|
|
+ const targetState = editor.getTargetState().copy().setFirstParagraphIndex(actionData.firstParagraph).setLastParagraphIndex(actionData.lastParagraph).setStart(actionData.start).setEnd(actionData.end);
|
|
|
|
|
+
|
|
|
|
|
+ editor.select(targetState);
|
|
|
|
|
+ } catch(error) {
|
|
|
|
|
+ console.error('Error selecting editor range:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async cleanup() {
|
|
|
|
|
+ if(this.browser)
|
|
|
|
|
+ await this.browser.close();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = CollaborativeActions;
|