Files
penpot/plugins/apps/e2e/src/utils/agent.ts
2026-02-13 13:17:08 +01:00

200 lines
5.2 KiB
TypeScript

import puppeteer, { ConsoleMessage } from 'puppeteer';
import { PenpotApi } from './api';
import { getFileUrl } from './get-file-url';
import { idObjectToArray } from './clean-id';
import { Shape } from '../models/shape.model';
const screenshotsEnable = process.env['E2E_SCREENSHOTS'] === 'true';
function replaceIds(shapes: Shape[]) {
let id = 1;
const getId = () => {
return String(id++);
};
function replaceChildrenId(id: string, newId: string) {
for (const node of shapes) {
if (node.parentId === id) {
node.parentId = newId;
}
if (node.frameId === id) {
node.frameId = newId;
}
if (node.shapes) {
node.shapes = node.shapes?.map((shapeId) => {
return shapeId === id ? newId : shapeId;
});
}
if (node.layoutGridCells) {
node.layoutGridCells = idObjectToArray(node.layoutGridCells, newId);
}
}
}
for (const node of shapes) {
const previousId = node.id;
node.id = getId();
replaceChildrenId(previousId, node.id);
}
}
export async function Agent() {
console.log('Initializing Penpot API...');
const penpotApi = await PenpotApi();
console.log('Creating file...');
const file = await penpotApi.createFile();
console.log('File created with id:', file['~:id']);
const fileUrl = getFileUrl(file);
console.log('File URL:', fileUrl);
console.log('Launching browser...');
const browser = await puppeteer.launch({
headless: process.env['E2E_HEADLESS'] !== 'false',
args: ['--ignore-certificate-errors'],
});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
await page.setExtraHTTPHeaders({
'X-Client': 'plugins/e2e:puppeter',
});
console.log('Setting authentication cookie...');
page.setCookie({
name: 'auth-token',
value: penpotApi.getAuth().split('=')[1],
domain: 'localhost',
path: '/',
expires: (Date.now() + 3600 * 1000) / 1000,
});
console.log('Navigating to file URL...');
await page.goto(fileUrl);
await page.waitForSelector('[data-testid="viewport"]');
console.log('Page loaded and viewport selector found.');
page
.on('console', async (message) => {
console.log(`${message.type()} ${message.text()}`);
})
.on('pageerror', (message) => {
console.error('Page error:', message);
});
const finish = async () => {
console.log('Deleting file and closing browser...');
// TODO
// await penpotApi.deleteFile(file['~:id']);
if (process.env['E2E_CLOSE_BROWSER'] !== 'false') {
await browser.close();
}
console.log('Clean up done.');
};
return {
async runCode(
code: string,
options: {
screenshot?: string;
autoFinish?: boolean;
} = {
screenshot: '',
autoFinish: true,
},
) {
const autoFinish = options.autoFinish ?? true;
console.log('Running plugin code...');
await page.evaluate((testingPlugin) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).ɵloadPlugin({
pluginId: '00000000-0000-0000-0000-000000000000',
name: 'Test',
code: `
(${testingPlugin})();
`,
icon: '',
description: '',
permissions: [
'content:read',
'content:write',
'library:read',
'library:write',
'user:read',
'comment:read',
'comment:write',
'allow:downloads',
'allow:localstorage',
],
});
}, code);
if (options.screenshot && screenshotsEnable) {
console.log('Taking screenshot:', options.screenshot);
await page.screenshot({
path: 'screenshots/' + options.screenshot + '.png',
});
}
const result = await new Promise((resolve) => {
const handleConsole = async (msg: ConsoleMessage) => {
const args = (await Promise.all(
msg.args().map((arg) => arg.jsonValue()),
)) as unknown[];
const type = args[0];
const data = args[1];
if (type !== 'objects' || !data || typeof data !== 'object') {
console.log('Invalid console message, waiting for valid one...');
page.once('console', handleConsole);
return;
}
const result = Object.values(data) as Shape[];
replaceIds(result);
console.log('IDs replaced in result.');
resolve(result);
};
page.once('console', handleConsole);
console.log('Evaluating debug.dump_objects...');
page.evaluate(`
debug.dump_objects();
`);
});
await page.waitForNetworkIdle({ idleTime: 2000 });
// Wait for the update-file API call to complete
if (process.env['E2E_WAIT_API_RESPONSE'] === 'true') {
await page.waitForResponse(
(response) =>
response.url().includes('api/main/methods/update-file') &&
response.status() === 200,
{ timeout: 10000 },
);
}
if (autoFinish) {
console.log('Auto finish enabled. Cleaning up...');
await finish();
}
return result;
},
finish,
};
}