forked from ungleich-public/dynamicweb
273 lines
8.8 KiB
JavaScript
273 lines
8.8 KiB
JavaScript
// @ts-check
|
|
const playwright = require('@playwright/test');
|
|
const baseTest = playwright.test;
|
|
const expect = playwright.expect;
|
|
|
|
/**
|
|
* Configuration settings for django-cms tests
|
|
*/
|
|
const settings = {
|
|
baseUrl: process.env.BASE_URL || 'http://localhost:8000',
|
|
get editUrl() {
|
|
return `${this.baseUrl}/en/?toolbar_on`;
|
|
},
|
|
get adminUrl() {
|
|
return `${this.baseUrl}/en/admin/`;
|
|
},
|
|
adminTitle: 'Django site admin',
|
|
credentials: {
|
|
username: process.env.TEST_USERNAME || 'admin',
|
|
password: process.env.TEST_PASSWORD || 'admin'
|
|
},
|
|
content: {
|
|
page: {
|
|
title: 'Test Page',
|
|
text: 'Test content text for the page'
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate a random string for testing
|
|
* @param {Object} options - Options for string generation
|
|
* @param {number} options.length - Length of the string
|
|
* @param {boolean} options.withWhitespaces - Include whitespaces
|
|
* @returns {string} Random string
|
|
*/
|
|
function randomString({ length = 10, withWhitespaces = true } = {}) {
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
const allChars = withWhitespaces ? chars + ' ' : chars;
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
result += allChars.charAt(Math.floor(Math.random() * allChars.length));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Extended test fixtures with CMS helpers
|
|
*/
|
|
const test = baseTest.extend({
|
|
/**
|
|
* Authenticated page that logs in before each test
|
|
*/
|
|
authenticatedPage: async ({ page }, use) => {
|
|
// Navigate to admin login page to get CSRF token
|
|
await page.goto(settings.adminUrl);
|
|
|
|
// Clear localStorage but keep cookies (we need CSRF token)
|
|
await page.evaluate(() => localStorage.clear());
|
|
|
|
// Wait for login form to be ready
|
|
await page.waitForSelector('input[name="username"]');
|
|
|
|
// Fill login form
|
|
await page.fill('input[name="username"]', settings.credentials.username);
|
|
await page.fill('input[name="password"]', settings.credentials.password);
|
|
await page.click('input[type="submit"]');
|
|
|
|
// Wait for successful login (redirect to admin)
|
|
await page.waitForURL(/\/admin\//, { timeout: 10000 });
|
|
|
|
// Use the authenticated page
|
|
await use(page);
|
|
|
|
// Logout after test - just clear cookies instead of POST to logout
|
|
await page.context().clearCookies();
|
|
},
|
|
|
|
/**
|
|
* CMS helper utilities
|
|
*/
|
|
cms: async ({ page }, use) => {
|
|
const helpers = {
|
|
/**
|
|
* Login to CMS
|
|
*/
|
|
login: async () => {
|
|
await page.goto(settings.adminUrl);
|
|
await page.evaluate(() => localStorage.clear());
|
|
await page.waitForSelector('input[name="username"]');
|
|
await page.fill('input[name="username"]', settings.credentials.username);
|
|
await page.fill('input[name="password"]', settings.credentials.password);
|
|
await page.click('input[type="submit"]');
|
|
await page.waitForURL(/\/admin\//, { timeout: 10000 });
|
|
},
|
|
|
|
/**
|
|
* Logout from CMS
|
|
*/
|
|
logout: async () => {
|
|
// Clear cookies to logout (POST to logout requires CSRF token)
|
|
await page.context().clearCookies();
|
|
await page.waitForTimeout(500);
|
|
},
|
|
|
|
/**
|
|
* Add a new page
|
|
* @param {Object} options - Page options
|
|
* @param {string} options.title - Page title
|
|
* @param {string} options.language - Page language
|
|
*/
|
|
addPage: async ({ title = 'Test Page', language = 'en' } = {}) => {
|
|
await page.goto(`${settings.baseUrl}/${language}/`);
|
|
await page.waitForSelector('.cms-toolbar-expanded', { timeout: 10000 });
|
|
await page.click('a[href*="/cms_wizard/create/"][data-rel="modal"]');
|
|
|
|
// Check if wizard modal appears (for first page)
|
|
const modalVisible = await page.isVisible('.cms-modal');
|
|
if (modalVisible) {
|
|
await page.click('.cms-modal-foot a.cms-btn.cms-btn-action.default');
|
|
await page.waitForSelector('.cms-modal iframe');
|
|
|
|
const frame = page.frameLocator('.cms-modal iframe');
|
|
await frame.locator('#id_1-title').fill(title);
|
|
await page.click('.cms-modal-foot a.cms-btn.cms-btn-action.default');
|
|
|
|
await page.waitForSelector('.cms-ready');
|
|
// the wizard navigates to the page it just created; let that finish
|
|
// before the spec drives the page itself
|
|
await page.waitForLoadState('networkidle');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Enter edit mode from a preview page. The "Edit" button is a link, so
|
|
* this is a navigation and has to be awaited as one.
|
|
*/
|
|
enterEditMode: async () => {
|
|
await page.waitForSelector('.cms-toolbar');
|
|
const editButton = page.locator('.cms-btn-switch-edit');
|
|
|
|
if (await editButton.count()) {
|
|
await editButton.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('.cms-toolbar');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Open the structure board. Edit mode alone only renders it - it stays
|
|
* hidden until the toolbar's mode switcher is used.
|
|
*/
|
|
openStructureBoard: async () => {
|
|
await page.click('.cms-toolbar-item-cms-mode-switcher a');
|
|
await page.waitForSelector('.cms-structure', { state: 'visible' });
|
|
},
|
|
|
|
/**
|
|
* Open a top level toolbar menu ("Page", "Language", ...) so that its
|
|
* items become clickable.
|
|
* @param {string} label - the menu's label
|
|
*/
|
|
openMenu: async (label) => {
|
|
await page
|
|
.locator('.cms-toolbar-item-navigation > li > a')
|
|
.filter({ hasText: label })
|
|
.first()
|
|
.click();
|
|
},
|
|
|
|
/**
|
|
* Remove a page
|
|
*/
|
|
removePage: async () => {
|
|
// Navigate to page tree
|
|
await page.goto(`${settings.baseUrl}/en/admin/cms/page/`);
|
|
|
|
// Find and delete the first page
|
|
const deleteButton = page.locator('.deletelink').first();
|
|
if (await deleteButton.isVisible()) {
|
|
await deleteButton.click();
|
|
await page.waitForSelector('input[value="Yes, I\'m sure"]');
|
|
await page.click('input[value="Yes, I\'m sure"]');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add a plugin to the page
|
|
* @param {Object} options - Plugin options
|
|
* @param {string} options.type - Plugin type
|
|
* @param {Object} options.content - Plugin content
|
|
*/
|
|
addPlugin: async ({ type = 'TextPlugin', content = {} } = {}) => {
|
|
await page.waitForSelector('.cms-toolbar-expanded');
|
|
|
|
// Switch to structure mode
|
|
await helpers.switchTo('structure');
|
|
|
|
// Click add plugin button
|
|
await page.click('.cms-submenu-add [data-cms-tooltip="Add plugin"]');
|
|
await page.waitForSelector('.cms-plugin-picker');
|
|
|
|
// Search for plugin type
|
|
const pluginName = type.replace('Plugin', '');
|
|
await page.fill('.cms-quicksearch input', pluginName);
|
|
await page.click(`text="${pluginName}"`);
|
|
|
|
// Fill plugin form
|
|
await page.waitForSelector('.cms-modal iframe');
|
|
const frame = page.frameLocator('.cms-modal iframe');
|
|
|
|
for (const [key, value] of Object.entries(content)) {
|
|
await frame.locator(`#${key}`).fill(value);
|
|
}
|
|
|
|
// Save plugin
|
|
await page.click('.cms-modal-buttons .cms-btn-action');
|
|
await page.waitForLoadState('networkidle');
|
|
},
|
|
|
|
/**
|
|
* Switch between structure and content mode
|
|
* @param {string} mode - 'structure' or 'content'
|
|
*/
|
|
switchTo: async (mode) => {
|
|
const currentMode = await page.evaluate(() => {
|
|
return window.CMS?.config?.mode || 'edit';
|
|
});
|
|
|
|
if (mode === 'structure' && currentMode !== 'structure') {
|
|
await page.keyboard.press(' ');
|
|
await page.waitForSelector('.cms-structure', { state: 'visible' });
|
|
} else if (mode === 'content' && currentMode === 'structure') {
|
|
await page.keyboard.press(' ');
|
|
await page.waitForSelector('.cms-structure', { state: 'hidden' });
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Wait until content is refreshed
|
|
*/
|
|
waitUntilContentIsRefreshed: async () => {
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(500); // Small delay for CMS to update
|
|
},
|
|
|
|
/**
|
|
* Open sideframe
|
|
* @param {string} url - URL to open in sideframe
|
|
*/
|
|
openSideframe: async (url) => {
|
|
await page.evaluate((sideframeUrl) => {
|
|
window.CMS.API.Sideframe.open({
|
|
url: sideframeUrl
|
|
});
|
|
}, url);
|
|
await page.waitForSelector('.cms-sideframe-frame');
|
|
},
|
|
|
|
/**
|
|
* Close sideframe
|
|
*/
|
|
closeSideframe: async () => {
|
|
await page.click('.cms-sideframe-close');
|
|
await page.waitForSelector('.cms-sideframe', { state: 'hidden' });
|
|
}
|
|
};
|
|
|
|
await use(helpers);
|
|
}
|
|
});
|
|
|
|
module.exports = { test, expect, settings, randomString };
|