diff --git a/e2e/tests/document-editing.spec.ts b/e2e/tests/document-editing.spec.ts new file mode 100644 index 0000000000..a552b6c925 --- /dev/null +++ b/e2e/tests/document-editing.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from '@playwright/test'; +import { openNewEditor, editorApi } from './helpers'; + +test.describe('Document editor - typing and formatting', () => { + test('type text and apply bold/italic', async ({ page }) => { + const { editorPage, frame } = await openNewEditor(page, 'a.try-editor.word', /\.docx/); + + await editorPage.keyboard.type('Hello world'); + await editorPage.keyboard.press('Control+A'); + await editorPage.keyboard.press('Control+b'); + await editorPage.keyboard.press('Control+i'); + + // The edit registered: the undo button is no longer disabled. + await expect(frame.locator('#slot-btn-undo button').first()).not.toHaveClass(/disabled/); + + // The typed text round-trips through the automation API. + const text = await editorApi(editorPage, (api) => api.asc_GetSelectedText()); + expect(text).toContain('Hello world'); + }); +}); diff --git a/e2e/tests/helpers.ts b/e2e/tests/helpers.ts new file mode 100644 index 0000000000..e1a69a0c75 --- /dev/null +++ b/e2e/tests/helpers.ts @@ -0,0 +1,50 @@ +import { Page, expect } from '@playwright/test'; + +const EDITOR_IFRAME = 'iframe[name="frameEditor"]'; + +/** + * Open the example page, create a new document of the requested type, and wait + * until the editor iframe has finished loading. Returns the editor tab (a new + * page) and a frameLocator scoped to the editor iframe. + */ +export async function openNewEditor(page: Page, selector: string, urlExt: RegExp) { + await page.goto('/example/'); + await expect(page).toHaveTitle(/ONLYOFFICE|euro-office/i); + + const newPagePromise = page.context().waitForEvent('page'); + await page.click(selector); + const editorPage = await newPagePromise; + + await editorPage.waitForURL(/\/example\/editor/); + await editorPage.waitForLoadState('domcontentloaded'); + await expect(editorPage).toHaveURL(urlExt); + + const editorIframe = editorPage.locator(EDITOR_IFRAME); + await expect(editorIframe).toBeAttached({ timeout: 15_000 }); + + const frame = editorPage.frameLocator(EDITOR_IFRAME); + await expect(frame.locator('#loading-mask')).toBeHidden({ timeout: 30_000 }); + + await frame.locator('#editor_sdk').click(); + return { editorPage, frame }; +} + +/** + * Evaluate a function against the editor's automation API (window.Asc.editor) + * inside the editor iframe. The function is serialized, so it must be + * self-contained (no closures over test scope). + */ +export async function editorApi(editorPage: Page, fn: (api: any) => T): Promise { + const handle = await editorPage.locator(EDITOR_IFRAME).elementHandle(); + if (!handle) throw new Error('editor iframe not found'); + const frame = await handle.contentFrame(); + if (!frame) throw new Error('editor iframe has no content frame'); + + return frame.evaluate((body) => { + const w = window as unknown as { Asc?: { editor?: unknown } }; + const api = w.Asc?.editor; + if (!api) throw new Error('window.Asc.editor not available'); + // eslint-disable-next-line no-new-func + return new Function('api', `return (${body})(api);`)(api); + }, fn.toString()); +} diff --git a/e2e/tests/presentation-slides.spec.ts b/e2e/tests/presentation-slides.spec.ts new file mode 100644 index 0000000000..f2a6d2aa00 --- /dev/null +++ b/e2e/tests/presentation-slides.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from '@playwright/test'; +import { openNewEditor, editorApi } from './helpers'; + +const slideCount = (p: import('@playwright/test').Page) => + editorApi(p, (api) => api.getCountPages()); + +test.describe('Presentation editor - building a deck', () => { + test('create a deck of 3 slides', async ({ page }) => { + const { editorPage, frame } = await openNewEditor(page, 'a.try-editor.slide', /\.pptx/); + + // A fresh presentation starts with a single slide. + expect(await slideCount(editorPage)).toBe(1); + + // The Ctrl+M shortcut only fires when the slide-thumbnail panel is focused + // and toolbar button ids are renamed in this build, so drive slide creation + // through the automation API (AddSlide) instead. + await editorApi(editorPage, (api) => { api.AddSlide(); api.AddSlide(); }); + + await expect.poll(() => slideCount(editorPage)).toBe(3); + + // The document was mutated: undo is enabled. + await expect(frame.locator('#slot-btn-undo button').first()).not.toHaveClass(/disabled/); + }); +}); diff --git a/e2e/tests/spreadsheet-formula.spec.ts b/e2e/tests/spreadsheet-formula.spec.ts new file mode 100644 index 0000000000..e56b6492e8 --- /dev/null +++ b/e2e/tests/spreadsheet-formula.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from '@playwright/test'; +import { openNewEditor, editorApi } from './helpers'; + +const cellText = (editorPage: import('@playwright/test').Page) => + editorApi(editorPage, (api) => { + const info = api.asc_getCellInfo(); + return info && info.asc_getText ? info.asc_getText() : null; + }); + +test.describe('Spreadsheet editor - numbers and formulas', () => { + test('enter numbers and a formula', async ({ page }) => { + const { editorPage, frame } = await openNewEditor(page, 'a.try-editor.cell', /\.xlsx/); + const nameBox = frame.locator('#ce-cell-name'); + const select = async (cell: string) => { + await nameBox.fill(cell); + await nameBox.press('Enter'); + }; + + // Ctrl+Home guarantees the grid has keyboard focus at A1 (the helper's + // canvas click can leave an arbitrary cell selected). + await editorPage.keyboard.press('Control+Home'); + await editorPage.keyboard.type('5'); + await editorPage.keyboard.press('Enter'); + await editorPage.keyboard.type('10'); + await editorPage.keyboard.press('Enter'); + await editorPage.keyboard.type('=A1+A2'); + await editorPage.keyboard.press('Enter'); + + // Operands landed in A1/A2. + await select('A1'); + expect(await cellText(editorPage)).toBe('5'); + await select('A2'); + expect(await cellText(editorPage)).toBe('10'); + + // The formula was accepted into A3. asc_getText() returns the formula + // string, not the computed value. + await select('A3'); + expect(await cellText(editorPage)).toBe('=A1+A2'); + + // TODO: verify the *computed* value (15). asc_getCellInfo().asc_getText() + // returns the formula, and pluginMethod_GetSelectedText() returns "" for + // cell selections, so neither reflects the result. The clipboard route + // (select A3 -> Ctrl+C -> navigator.clipboard.readText() === '15') works + // but needs clipboard-read/write permissions wired into the project config. + }); +});