import { test, expect, Page } from '@playwright/test'; const LOGIN_URL = 'http://192.168.1.105:8080/'; const EDITOR_URL = 'http://192.168.1.105:8080/site-builder/?site_id=1'; const LIVE_URL = 'http://testsite.local/'; const USERNAME = 'root'; const PASSWORD = 'Jk@44351100!'; // Helper: login to WHP async function login(page: Page) { await page.goto(LOGIN_URL); if (page.url().includes('login')) { await page.fill('input[name="user"]', USERNAME); await page.fill('input[name="password"]', PASSWORD); await page.click('button[type="submit"], input[type="submit"]'); await page.waitForURL('**/index.php**'); } } // Helper: open site builder async function openEditor(page: Page) { await page.goto(EDITOR_URL); await page.waitForSelector('.editor-canvas', { timeout: 10000 }); await page.waitForTimeout(2000); // Let Craft.js initialize } // Helper: add a block by double-clicking it in the blocks panel async function addBlock(page: Page, blockLabel: string) { // Make sure blocks tab is active const blocksTab = page.locator('.panel-tab', { hasText: 'Blocks' }); await blocksTab.click(); await page.waitForTimeout(300); // Expand ALL collapsed categories first let collapsed = page.locator('.block-category-header.collapsed'); let count = await collapsed.count(); for (let i = 0; i < count; i++) { await collapsed.nth(0).click(); // Always click first collapsed (it changes after click) await page.waitForTimeout(200); collapsed = page.locator('.block-category-header.collapsed'); // Re-query } await page.waitForTimeout(300); // Find the block by title attribute const block = page.locator(`.block-item[title*="${blockLabel}"]`).first(); // Scroll it into view await block.scrollIntoViewIfNeeded(); await page.waitForTimeout(200); await block.dblclick({ force: true }); await page.waitForTimeout(800); } // Helper: check console for errors async function checkNoErrors(page: Page): Promise { const errors: string[] = []; page.on('pageerror', (error) => { errors.push(error.message); }); return errors; } // Helper: click on an element in the canvas async function selectCanvasElement(page: Page, selector: string) { const el = page.locator(`.canvas-device-frame ${selector}`).first(); if (await el.count() > 0) { await el.click(); await page.waitForTimeout(500); } return el; } // Helper: check that the Styles panel shows controls (not "No controls") async function verifyStylesPanel(page: Page) { const stylesTab = page.locator('.panel-tab', { hasText: 'Styles' }); await stylesTab.click(); await page.waitForTimeout(300); const panelContent = page.locator('.panel-right .panel-content'); const text = await panelContent.textContent(); return text || ''; } // ============================================================ // TESTS // ============================================================ test.describe('Site Builder - Authentication', () => { test('can login to WHP', async ({ page }) => { await login(page); expect(page.url()).toContain('index.php'); }); test('can open site builder', async ({ page }) => { await login(page); await openEditor(page); expect(page.url()).toContain('site-builder'); await expect(page.locator('.editor-canvas')).toBeVisible(); }); }); test.describe('Site Builder - Block Categories', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('has all 5 block categories', async ({ page }) => { const categories = page.locator('.block-category-header'); const texts = await categories.allTextContents(); expect(texts.join(' ')).toContain('Basic'); expect(texts.join(' ')).toContain('Layout'); expect(texts.join(' ')).toContain('Sections'); expect(texts.join(' ')).toContain('Media'); expect(texts.join(' ')).toContain('Forms'); }); test('has correct block count', async ({ page }) => { // Expand all categories const headers = page.locator('.block-category-header.collapsed'); for (let i = 0; i < await headers.count(); i++) { await headers.nth(i).click(); await page.waitForTimeout(200); } const blocks = page.locator('.block-item'); const count = await blocks.count(); expect(count).toBeGreaterThanOrEqual(35); }); }); test.describe('Site Builder - Basic Blocks', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('can add Heading block', async ({ page }) => { await addBlock(page, 'Heading'); const heading = page.locator('.canvas-device-frame h2').first(); await expect(heading).toBeVisible(); }); test('can add Text block', async ({ page }) => { await addBlock(page, 'Text'); const text = page.locator('.canvas-device-frame p'); expect(await text.count()).toBeGreaterThan(0); }); test('can add Button block', async ({ page }) => { await addBlock(page, 'Button'); const button = page.locator('.canvas-device-frame a[style*="inline-block"]'); expect(await button.count()).toBeGreaterThan(0); }); test('can add Divider block', async ({ page }) => { await addBlock(page, 'Divider'); const divider = page.locator('.canvas-device-frame hr'); expect(await divider.count()).toBeGreaterThan(0); }); test('can add Spacer block', async ({ page }) => { await addBlock(page, 'Spacer'); const spacer = page.locator('.canvas-device-frame div[style*="height"]'); expect(await spacer.count()).toBeGreaterThan(0); }); test('can add Icon block', async ({ page }) => { await addBlock(page, 'Icon'); const icon = page.locator('.canvas-device-frame .fa'); expect(await icon.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Layout Blocks', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('can add 2 Column layout', async ({ page }) => { await addBlock(page, '2 Columns'); const flexContainer = page.locator('.canvas-device-frame div[style*="display: flex"]'); expect(await flexContainer.count()).toBeGreaterThan(0); }); test('can add 4 Column layout', async ({ page }) => { await addBlock(page, '4 Columns'); await page.waitForTimeout(500); const flexContainer = page.locator('.canvas-device-frame div[style*="display: flex"]'); expect(await flexContainer.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Section Blocks', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('can add Hero block', async ({ page }) => { await addBlock(page, 'Hero'); const hero = page.locator('.canvas-device-frame section[style*="min-height"]'); expect(await hero.count()).toBeGreaterThan(0); }); test('Hero shows settings when selected', async ({ page }) => { await addBlock(page, 'Hero'); await page.locator('.canvas-device-frame section[style*="min-height"]').first().click(); await page.waitForTimeout(500); const panelText = await verifyStylesPanel(page); expect(panelText).toContain('Hero'); expect(panelText).toContain('Heading'); }); test('can add Pricing Table', async ({ page }) => { await addBlock(page, 'Pricing'); await page.waitForTimeout(500); const pricing = page.locator('.canvas-device-frame div[style*="flex"]'); expect(await pricing.count()).toBeGreaterThan(0); }); test('can add Accordion', async ({ page }) => { await addBlock(page, 'Accordion'); await page.waitForTimeout(500); // Accordion should have clickable panels const panels = page.locator('.canvas-device-frame div[style*="cursor: pointer"]'); expect(await panels.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Media Blocks', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('can add Image block', async ({ page }) => { await addBlock(page, 'Image'); const img = page.locator('.canvas-device-frame img'); expect(await img.count()).toBeGreaterThan(0); }); test('Image shows upload controls when selected', async ({ page }) => { await addBlock(page, 'Image'); await page.locator('.canvas-device-frame img').first().click(); await page.waitForTimeout(500); const panelText = await verifyStylesPanel(page); expect(panelText.toLowerCase()).toContain('image'); }); test('can add Video block', async ({ page }) => { await addBlock(page, 'Video'); await page.waitForTimeout(500); // Video block should render something (placeholder or iframe) const canvas = page.locator('.canvas-device-frame'); expect(await canvas.locator('*').count()).toBeGreaterThan(1); }); }); test.describe('Site Builder - Form Blocks', () => { test.beforeEach(async ({ page }) => { await login(page); await openEditor(page); }); test('can add Contact Form', async ({ page }) => { await addBlock(page, 'Contact Form'); await page.waitForTimeout(500); const form = page.locator('.canvas-device-frame form, .canvas-device-frame input'); expect(await form.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Save and Load', () => { test('can save and reload', async ({ page }) => { await login(page); await openEditor(page); // Add a heading with unique text await addBlock(page, 'Heading'); await page.waitForTimeout(500); // Click Save await page.locator('button', { hasText: 'Save' }).click(); await page.waitForTimeout(2000); // Reload await page.goto(EDITOR_URL); await page.waitForSelector('.editor-canvas', { timeout: 10000 }); await page.waitForTimeout(3000); // Verify canvas has content const canvas = page.locator('.canvas-device-frame'); const content = await canvas.textContent(); expect(content?.length).toBeGreaterThan(0); }); }); test.describe('Site Builder - Publish', () => { test('can publish site', async ({ page }) => { await login(page); await openEditor(page); // Click Publish const publishBtn = page.locator('button', { hasText: 'Publish' }); if (await publishBtn.count() > 0) { await publishBtn.click(); await page.waitForTimeout(3000); } // Check live site await page.goto(LIVE_URL); await page.waitForTimeout(2000); const body = await page.textContent('body'); expect(body?.length).toBeGreaterThan(0); }); }); test.describe('Site Builder - No Console Errors', () => { test('editor loads without JS errors', async ({ page }) => { const errors: string[] = []; page.on('pageerror', (error) => { // Ignore favicon 404 and non-critical errors if (!error.message.includes('favicon')) { errors.push(error.message); } }); await login(page); await openEditor(page); await page.waitForTimeout(3000); expect(errors).toHaveLength(0); }); }); test.describe('Site Builder - Color Controls', () => { test('Hero has full color picker (not just presets)', async ({ page }) => { await login(page); await openEditor(page); await addBlock(page, 'Hero'); await page.locator('.canvas-device-frame section').first().click(); await page.waitForTimeout(500); // Should have for full spectrum const colorInputs = page.locator('.panel-right input[type="color"]'); expect(await colorInputs.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Multi-Page', () => { test('can create and switch pages', async ({ page }) => { await login(page); await openEditor(page); // Go to Pages tab await page.locator('.panel-tab', { hasText: 'Pages' }).click(); await page.waitForTimeout(300); // Check for Add Page button const addPageBtn = page.locator('button, div', { hasText: /add page/i }); expect(await addPageBtn.count()).toBeGreaterThan(0); }); }); test.describe('Site Builder - Templates', () => { test('template modal opens and shows templates', async ({ page }) => { await login(page); await openEditor(page); // Click Templates button await page.locator('button', { hasText: 'Templates' }).click(); await page.waitForTimeout(1000); // Should see template cards const modal = page.locator('[style*="position: fixed"]'); expect(await modal.count()).toBeGreaterThan(0); // Close with Escape await page.keyboard.press('Escape'); }); }); test.describe('Site Builder - Header/Footer', () => { test('header and footer zones show on canvas', async ({ page }) => { await login(page); await openEditor(page); // Should see zone preview areas const zones = page.locator('[data-zone-preview]'); // At minimum the footer zone should show (header may be empty) expect(await zones.count()).toBeGreaterThanOrEqual(0); }); });