🐛 Fix e2e tests for plugins

This commit is contained in:
Juanfran 2026-02-13 09:35:56 +01:00 committed by Andrey Antukh
parent bd7f4dca3a
commit 449aa65f8d
12 changed files with 435 additions and 307 deletions

View File

@ -126,6 +126,6 @@
(defn check-permission (defn check-permission
[plugin-id permission] [plugin-id permission]
(or (= plugin-id "TEST") (or (= plugin-id "00000000-0000-0000-0000-000000000000")
(let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])] (let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])]
(contains? permissions permission)))) (contains? permissions permission))))

View File

@ -18,7 +18,7 @@
(let [;; ==== Setup (let [;; ==== Setup
store (ths/setup-store (cthf/sample-file :file1 :page-label :page1)) store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
^js context (api/create-context "TEST") ^js context (api/create-context "00000000-0000-0000-0000-000000000000")
_ (set! st/state store) _ (set! st/state store)

View File

@ -28,5 +28,5 @@ export default [
files: ['**/*.js', '**/*.jsx'], files: ['**/*.js', '**/*.jsx'],
rules: {}, rules: {},
}, },
{ ignores: ['vite.config.ts'] }, { ignores: ['vite.config.ts', 'vitest.setup.ts'] },
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import comments from './plugins/create-comments';
import { Agent } from './utils/agent'; import { Agent } from './utils/agent';
describe('Plugins', () => { describe('Plugins', () => {
it.only('create board - text - rectable', async () => { it('create board - text - rectable', async () => {
const agent = await Agent(); const agent = await Agent();
const result = await agent.runCode(testingPlugin.toString(), { const result = await agent.runCode(testingPlugin.toString(), {
screenshot: 'create-board-text-rect', screenshot: 'create-board-text-rect',
@ -29,6 +29,7 @@ describe('Plugins', () => {
it('create grid layout', async () => { it('create grid layout', async () => {
const agent = await Agent(); const agent = await Agent();
const result = await agent.runCode(grid.toString(), { const result = await agent.runCode(grid.toString(), {
screenshot: 'create-gridlayout', screenshot: 'create-gridlayout',
}); });
@ -83,9 +84,9 @@ describe('Plugins', () => {
it('comments', async () => { it('comments', async () => {
const agent = await Agent(); const agent = await Agent();
console.log(comments.toString());
const result = await agent.runCode(comments.toString(), { const result = await agent.runCode(comments.toString(), {
screenshot: 'create-comments', screenshot: 'create-comments',
avoidSavedStatus: true,
}); });
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });

View File

@ -1,4 +1,4 @@
import puppeteer from 'puppeteer'; import puppeteer, { ConsoleMessage } from 'puppeteer';
import { PenpotApi } from './api'; import { PenpotApi } from './api';
import { getFileUrl } from './get-file-url'; import { getFileUrl } from './get-file-url';
import { idObjectToArray } from './clean-id'; import { idObjectToArray } from './clean-id';
@ -56,7 +56,10 @@ export async function Agent() {
console.log('File URL:', fileUrl); console.log('File URL:', fileUrl);
console.log('Launching browser...'); console.log('Launching browser...');
const browser = await puppeteer.launch({args: ['--ignore-certificate-errors']}); const browser = await puppeteer.launch({
headless: process.env['E2E_HEADLESS'] !== 'false',
args: ['--ignore-certificate-errors'],
});
const page = await browser.newPage(); const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 }); await page.setViewport({ width: 1920, height: 1080 });
@ -88,8 +91,11 @@ export async function Agent() {
const finish = async () => { const finish = async () => {
console.log('Deleting file and closing browser...'); console.log('Deleting file and closing browser...');
await penpotApi.deleteFile(file['~:id']); // TODO
await browser.close(); // await penpotApi.deleteFile(file['~:id']);
if (process.env['E2E_CLOSE_BROWSER'] !== 'false') {
await browser.close();
}
console.log('Clean up done.'); console.log('Clean up done.');
}; };
@ -99,11 +105,9 @@ export async function Agent() {
options: { options: {
screenshot?: string; screenshot?: string;
autoFinish?: boolean; autoFinish?: boolean;
avoidSavedStatus?: boolean;
} = { } = {
screenshot: '', screenshot: '',
autoFinish: true, autoFinish: true,
avoidSavedStatus: false,
}, },
) { ) {
const autoFinish = options.autoFinish ?? true; const autoFinish = options.autoFinish ?? true;
@ -112,28 +116,27 @@ export async function Agent() {
await page.evaluate((testingPlugin) => { await page.evaluate((testingPlugin) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).ɵloadPlugin({ (globalThis as any).ɵloadPlugin({
pluginId: 'TEST', pluginId: '00000000-0000-0000-0000-000000000000',
name: 'Test', name: 'Test',
code: ` code: `
(${testingPlugin})(); (${testingPlugin})();
`, `,
icon: '', icon: '',
description: '', description: '',
permissions: ['content:read', 'content:write'], permissions: [
'content:read',
'content:write',
'library:read',
'library:write',
'user:read',
'comment:read',
'comment:write',
'allow:downloads',
'allow:localstorage',
],
}); });
}, code); }, code);
if (!options.avoidSavedStatus) {
console.log('Waiting for save status...');
await page.waitForSelector(
'.main_ui_workspace_right_header__saved-status',
{
timeout: 10000,
},
);
console.log('Save status found.');
}
if (options.screenshot && screenshotsEnable) { if (options.screenshot && screenshotsEnable) {
console.log('Taking screenshot:', options.screenshot); console.log('Taking screenshot:', options.screenshot);
await page.screenshot({ await page.screenshot({
@ -141,30 +144,55 @@ export async function Agent() {
}); });
} }
return new Promise((resolve) => { const result = await new Promise((resolve) => {
page.once('console', async (msg) => { const handleConsole = async (msg: ConsoleMessage) => {
const args = (await Promise.all( const args = (await Promise.all(
msg.args().map((arg) => arg.jsonValue()), msg.args().map((arg) => arg.jsonValue()),
)) as Record<string, unknown>[]; )) as unknown[];
const result = Object.values(args[1]) as Shape[]; 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); replaceIds(result);
console.log('IDs replaced in result.'); console.log('IDs replaced in result.');
resolve(result); resolve(result);
};
if (autoFinish) { page.once('console', handleConsole);
console.log('Auto finish enabled. Cleaning up...');
finish();
}
});
console.log('Evaluating debug.dump_objects...'); console.log('Evaluating debug.dump_objects...');
page.evaluate(` page.evaluate(`
debug.dump_objects(); 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, finish,
}; };

View File

@ -1,5 +1,4 @@
import { FileRpc } from '../models/file-rpc.model'; import { FileRpc } from '../models/file-rpc.model';
const apiUrl = 'https://localhost:3449'; const apiUrl = 'https://localhost:3449';
export async function PenpotApi() { export async function PenpotApi() {
@ -8,8 +7,8 @@ export async function PenpotApi() {
} }
const body = JSON.stringify({ const body = JSON.stringify({
'email': process.env['E2E_LOGIN_EMAIL'], email: process.env['E2E_LOGIN_EMAIL'],
'password': process.env['E2E_LOGIN_PASSWORD'], password: process.env['E2E_LOGIN_PASSWORD'],
}); });
const resultLoginRequest = await fetch( const resultLoginRequest = await fetch(
@ -18,25 +17,18 @@ export async function PenpotApi() {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: body body: body,
}, },
); );
console.log("AAAAAAAAAAAA", 1, apiUrl)
// console.log("AAAAAAAAAAAA", 2, resultLoginRequest);
console.dir(resultLoginRequest.headers, {depth:20});
console.log('Document Cookies:', window.document.cookie);
const loginData = await resultLoginRequest.json(); const loginData = await resultLoginRequest.json();
const authToken = resultLoginRequest.headers const authToken = resultLoginRequest.headers
.get('set-cookie') .getSetCookie()
?.split(';') .find((cookie: string) => cookie.startsWith('auth-token='))
.at(0); ?.split(';')[0];
if (!authToken) { if (!authToken) {
throw new Error('Login failed'); throw new Error('Login failed');
@ -62,6 +54,9 @@ export async function PenpotApi() {
'fdata/objects-map', 'fdata/objects-map',
'fdata/pointer-map', 'fdata/pointer-map',
'fdata/shape-data-type', 'fdata/shape-data-type',
'fdata/path-data',
'design-tokens/v1',
'variants/v1',
'components/v2', 'components/v2',
'styles/v2', 'styles/v2',
'layout/grid', 'layout/grid',
@ -72,7 +67,9 @@ export async function PenpotApi() {
}, },
); );
return (await createFileRequest.json()) as FileRpc; const fileData = (await createFileRequest.json()) as FileRpc;
console.log('File data received:', fileData);
return fileData;
}, },
deleteFile: async (fileId: string) => { deleteFile: async (fileId: string) => {
const deleteFileRequest = await fetch( const deleteFileRequest = await fetch(

View File

@ -6,5 +6,5 @@ export function getFileUrl(file: FileRpc) {
const fileId = cleanId(file['~:id']); const fileId = cleanId(file['~:id']);
const pageId = cleanId(file['~:data']['~:pages'][0]); const pageId = cleanId(file['~:data']['~:pages'][0]);
return `http://localhost:3449/#/workspace/${projectId}/${fileId}?page-id=${pageId}`; return `https://localhost:3449/#/workspace/${projectId}/${fileId}?page-id=${pageId}`;
} }

View File

@ -7,13 +7,27 @@ export default defineConfig({
testTimeout: 20000, testTimeout: 20000,
watch: false, watch: false,
globals: true, globals: true,
environment: 'happy-dom', environment: 'node',
environmentOptions: {
happyDOM: {
settings: {
disableCSSFileLoading: true,
disableJavaScriptFileLoading: true,
disableJavaScriptEvaluation: true,
enableFileSystemHttpRequests: false,
navigator: {
userAgent:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
},
},
},
},
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/e2e', reportsDirectory: '../coverage/e2e',
provider: 'v8', provider: 'v8',
}, },
setupFiles: ['dotenv/config', 'vitest.setup.ts'] setupFiles: ['dotenv/config', 'vitest.setup.ts'],
}, },
}); });

View File

@ -1,3 +1 @@
// import { vi } from 'vitest'; process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
window.location.href = 'https://localhost:3449';

View File

@ -9,7 +9,10 @@
```env ```env
E2E_LOGIN_EMAIL="test@penpot.app" E2E_LOGIN_EMAIL="test@penpot.app"
E2E_LOGIN_PASSWORD="123123123" E2E_LOGIN_PASSWORD="123123123"
E2E_SCREENSHOTS= "true" E2E_SCREENSHOTS="true" # Enable/disable screenshots (default: false)
E2E_HEADLESS="false" # Run browser in headless mode (default: true)
E2E_CLOSE_BROWSER="true" # Close browser after tests (default: true)
E2E_WAIT_API_RESPONSE="false" # Wait for update-file API response (default: false)
``` ```
2. **Run E2E Tests** 2. **Run E2E Tests**
@ -77,5 +80,5 @@
If you need to refresh all the snapshopts run the test with the update option: If you need to refresh all the snapshopts run the test with the update option:
```bash ```bash
pnpm run test:e2e -- --update pnpm run test:e2e --update
``` ```

View File

@ -28,7 +28,9 @@ export const initPluginsRuntime = (contextBuilder: (id: string) => Context) => {
try { try {
console.log('%c[PLUGINS] Initialize runtime', 'color: #008d7c'); console.log('%c[PLUGINS] Initialize runtime', 'color: #008d7c');
setContextBuilder(contextBuilder); setContextBuilder(contextBuilder);
globalThisAny$.ɵcontext = contextBuilder('TEST'); globalThisAny$.ɵcontext = contextBuilder(
'00000000-0000-0000-0000-000000000000',
);
globalThis.ɵloadPlugin = ɵloadPlugin; globalThis.ɵloadPlugin = ɵloadPlugin;
globalThis.ɵloadPluginByUrl = ɵloadPluginByUrl; globalThis.ɵloadPluginByUrl = ɵloadPluginByUrl;
globalThis.ɵunloadPlugin = ɵunloadPlugin; globalThis.ɵunloadPlugin = ɵunloadPlugin;