) => {
+ const searchParams = new URLSearchParams(page.url().split('?')[1]);
+ Object.keys(queryParameters).forEach((key) => {
+ searchParams.append(key, queryParameters[key]);
+ });
+ await page.goto(page.url()+"?"+ searchParams.toString());
+ await page.waitForSelector('iframe[name="ace_outer"]');
+}
+
+export const goToNewPad = async (page: Page) => {
+ // create a new pad before each test run
+ await page.goto('http://localhost:9001/p/'+"FRONTEND_TESTS"+randomInt(0, 1000));
+ await page.waitForSelector('iframe[name="ace_outer"]');
+}
+
+export const clearPadContent = async (page: Page) => {
+ await page.keyboard.down('Control');
+ await page.keyboard.press('A');
+ await page.keyboard.up('Control');
+ await page.keyboard.press('Delete');
+}
diff --git a/src/tests/frontend-new/helper/settingsHelper.ts b/src/tests/frontend-new/helper/settingsHelper.ts
new file mode 100644
index 000000000..729dd48f6
--- /dev/null
+++ b/src/tests/frontend-new/helper/settingsHelper.ts
@@ -0,0 +1,35 @@
+import {Page} from "@playwright/test";
+
+export const isSettingsShown = async (page: Page) => {
+ const classes = await page.locator('#settings').getAttribute('class')
+ return classes && classes.includes('popup-show')
+}
+
+
+export const showSettings = async (page: Page) => {
+ if(await isSettingsShown(page)) return
+ await page.locator("button[data-l10n-id='pad.toolbar.settings.title']").click()
+ await page.waitForFunction(`document.querySelector('#settings').classList.contains('popup-show')`)
+}
+
+export const hideSettings = async (page: Page) => {
+ if(!await isSettingsShown(page)) return
+ await page.locator("button[data-l10n-id='pad.toolbar.settings.title']").click()
+ await page.waitForFunction(`!document.querySelector('#settings').classList.contains('popup-show')`)
+}
+
+export const enableStickyChatviaSettings = async (page: Page) => {
+ const stickyChat = page.locator('#options-stickychat')
+ const checked = await stickyChat.isChecked()
+ if(checked) return
+ await stickyChat.check({force: true})
+ await page.waitForSelector('#options-stickychat:checked')
+}
+
+export const disableStickyChat = async (page: Page) => {
+ const stickyChat = page.locator('#options-stickychat')
+ const checked = await stickyChat.isChecked()
+ if(!checked) return
+ await stickyChat.uncheck({force: true})
+ await page.waitForSelector('#options-stickychat:not(:checked)')
+}
diff --git a/src/tests/frontend-new/specs/alphabet.spec.ts b/src/tests/frontend-new/specs/alphabet.spec.ts
new file mode 100644
index 000000000..fcd8f7f9d
--- /dev/null
+++ b/src/tests/frontend-new/specs/alphabet.spec.ts
@@ -0,0 +1,27 @@
+import {expect, Page, test} from "@playwright/test";
+import {clearPadContent, getPadBody, getPadOuter, goToNewPad} from "../helper/padHelper";
+
+test.beforeEach(async ({ page })=>{
+ // create a new pad before each test run
+ await goToNewPad(page);
+})
+
+test.describe('All the alphabet works n stuff', () => {
+ const expectedString = 'abcdefghijklmnopqrstuvwxyz';
+
+ test('when you enter any char it appears right', async ({page}) => {
+
+ // get the inner iframe
+ const innerFrame = await getPadBody(page!);
+
+ await innerFrame.click();
+
+ // delete possible old content
+ await clearPadContent(page!);
+
+
+ await page.keyboard.type(expectedString);
+ const text = await innerFrame.locator('div').innerText();
+ expect(text).toBe(expectedString);
+ });
+});
diff --git a/src/tests/frontend-new/specs/bold.spec.ts b/src/tests/frontend-new/specs/bold.spec.ts
new file mode 100644
index 000000000..6c1769da2
--- /dev/null
+++ b/src/tests/frontend-new/specs/bold.spec.ts
@@ -0,0 +1,50 @@
+import {expect, test} from "@playwright/test";
+import {randomInt} from "node:crypto";
+import {getPadBody, goToNewPad, selectAllText} from "../helper/padHelper";
+import exp from "node:constants";
+
+test.beforeEach(async ({ page })=>{
+ await goToNewPad(page);
+})
+
+test.describe('bold button', ()=>{
+
+ test('makes text bold on click', async ({page}) => {
+// get the inner iframe
+ const innerFrame = await getPadBody(page);
+
+ await innerFrame.click()
+ // Select pad text
+ await selectAllText(page);
+ await page.keyboard.type("Hi Etherpad");
+ await selectAllText(page);
+
+ // click the bold button
+ await page.locator("button[data-l10n-id='pad.toolbar.bold.title']").click();
+
+
+ // check if the text is bold
+ expect(await innerFrame.locator('b').innerText()).toBe('Hi Etherpad');
+ })
+
+ test('makes text bold on keypress', async ({page}) => {
+ // get the inner iframe
+ const innerFrame = await getPadBody(page);
+
+ await innerFrame.click()
+ // Select pad text
+ await selectAllText(page);
+ await page.keyboard.type("Hi Etherpad");
+ await selectAllText(page);
+
+ // Press CTRL + B
+ await page.keyboard.down('Control');
+ await page.keyboard.press('b');
+ await page.keyboard.up('Control');
+
+
+ // check if the text is bold
+ expect(await innerFrame.locator('b').innerText()).toBe('Hi Etherpad');
+ })
+
+})
diff --git a/src/tests/frontend-new/specs/change_user_name.spec.ts b/src/tests/frontend-new/specs/change_user_name.spec.ts
new file mode 100644
index 000000000..bf7ea95c3
--- /dev/null
+++ b/src/tests/frontend-new/specs/change_user_name.spec.ts
@@ -0,0 +1,35 @@
+import {expect, test} from "@playwright/test";
+import {randomInt} from "node:crypto";
+import {goToNewPad, sendChatMessage, setUserName, showChat, toggleUserList} from "../helper/padHelper";
+
+test.beforeEach(async ({ page })=>{
+ // create a new pad before each test run
+ await goToNewPad(page);
+})
+
+
+test("Remembers the username after a refresh", async ({page}) => {
+ await toggleUserList(page);
+ await setUserName(page,'😃')
+ await toggleUserList(page)
+
+ await page.reload();
+ await toggleUserList(page);
+ const usernameField = page.locator("input[data-l10n-id='pad.userlist.entername']");
+ await expect(usernameField).toHaveValue('😃');
+})
+
+
+test('Own user name is shown when you enter a chat', async ({page})=> {
+ const chatMessage = 'O hi';
+
+ await toggleUserList(page);
+ await setUserName(page,'😃');
+ await toggleUserList(page);
+
+ await showChat(page);
+ await sendChatMessage(page,chatMessage);
+ const chatText = await page.locator('#chattext').locator('p').innerText();
+ expect(chatText).toContain('😃')
+ expect(chatText).toContain(chatMessage)
+});
diff --git a/src/tests/frontend-new/specs/chat.spec.ts b/src/tests/frontend-new/specs/chat.spec.ts
new file mode 100644
index 000000000..4d4f1bd1c
--- /dev/null
+++ b/src/tests/frontend-new/specs/chat.spec.ts
@@ -0,0 +1,116 @@
+import {expect, test} from "@playwright/test";
+import {randomInt} from "node:crypto";
+import {
+ appendQueryParams,
+ disableStickyChatviaIcon,
+ enableStickyChatviaIcon,
+ getChatMessage,
+ getChatTime,
+ getChatUserName,
+ getCurrentChatMessageCount, goToNewPad, hideChat, isChatBoxShown, isChatBoxSticky,
+ sendChatMessage,
+ showChat,
+} from "../helper/padHelper";
+import {disableStickyChat, enableStickyChatviaSettings, hideSettings, showSettings} from "../helper/settingsHelper";
+
+
+test.beforeEach(async ({ page })=>{
+ await goToNewPad(page);
+})
+
+
+test('opens chat, sends a message, makes sure it exists on the page and hides chat', async ({page}) => {
+ const chatValue = "JohnMcLear"
+
+ // Open chat
+ await showChat(page);
+ await sendChatMessage(page, chatValue);
+
+ expect(await getCurrentChatMessageCount(page)).toBe(1);
+ const username = await getChatUserName(page)
+ const time = await getChatTime(page)
+ const chatMessage = await getChatMessage(page)
+
+ expect(username).toBe('unnamed:');
+ const regex = new RegExp('^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$');
+ expect(time).toMatch(regex);
+ expect(chatMessage).toBe(" "+chatValue);
+})
+
+test("makes sure that an empty message can't be sent", async function ({page}) {
+ const chatValue = 'mluto';
+
+ await showChat(page);
+
+ await sendChatMessage(page,"");
+ // Send a message
+ await sendChatMessage(page,chatValue);
+
+ expect(await getCurrentChatMessageCount(page)).toBe(1);
+
+ // check that the received message is not the empty one
+ const username = await getChatUserName(page)
+ const time = await getChatTime(page);
+ const chatMessage = await getChatMessage(page);
+
+ expect(username).toBe('unnamed:');
+ const regex = new RegExp('^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$');
+ expect(time).toMatch(regex);
+ expect(chatMessage).toBe(" "+chatValue);
+});
+
+test('makes chat stick to right side of the screen via settings, remove sticky via settings, close it', async ({page}) =>{
+ await showSettings(page);
+
+ await enableStickyChatviaSettings(page);
+ expect(await isChatBoxShown(page)).toBe(true);
+ expect(await isChatBoxSticky(page)).toBe(true);
+
+ await disableStickyChat(page);
+ expect(await isChatBoxShown(page)).toBe(true);
+ expect(await isChatBoxSticky(page)).toBe(false);
+ await hideSettings(page);
+ await hideChat(page);
+ expect(await isChatBoxShown(page)).toBe(false);
+ expect(await isChatBoxSticky(page)).toBe(false);
+});
+
+test('makes chat stick to right side of the screen via icon on the top right, ' +
+ 'remove sticky via icon, close it', async function ({page}) {
+ await showChat(page);
+
+ await enableStickyChatviaIcon(page);
+ expect(await isChatBoxShown(page)).toBe(true);
+ expect(await isChatBoxSticky(page)).toBe(true);
+
+ await disableStickyChatviaIcon(page);
+ expect(await isChatBoxShown(page)).toBe(true);
+ expect(await isChatBoxSticky(page)).toBe(false);
+
+ await hideChat(page);
+ expect(await isChatBoxSticky(page)).toBe(false);
+ expect(await isChatBoxShown(page)).toBe(false);
+});
+
+
+test('Checks showChat=false URL Parameter hides chat then' +
+ ' when removed it shows chat', async function ({page}) {
+
+ // get a new pad, but don't clear the cookies
+ await appendQueryParams(page, {
+ showChat: 'false'
+ });
+
+ const chaticon = page.locator('#chaticon')
+
+
+ // chat should be hidden.
+ expect(await chaticon.isVisible()).toBe(false);
+
+ // get a new pad, but don't clear the cookies
+ await goToNewPad(page);
+ const secondChatIcon = page.locator('#chaticon')
+
+ // chat should be visible.
+ expect(await secondChatIcon.isVisible()).toBe(true)
+});
diff --git a/src/tests/frontend-new/specs/delete.spec.ts b/src/tests/frontend-new/specs/delete.spec.ts
new file mode 100644
index 000000000..26eb3fbdb
--- /dev/null
+++ b/src/tests/frontend-new/specs/delete.spec.ts
@@ -0,0 +1,22 @@
+import {expect, test} from "@playwright/test";
+import {clearPadContent, getPadBody, goToNewPad} from "../helper/padHelper";
+
+test.beforeEach(async ({ page })=>{
+ // create a new pad before each test run
+ await goToNewPad(page);
+})
+
+
+test('delete keystroke', async ({page}) => {
+ const padText = "Hello World this is a test"
+ const body = await getPadBody(page)
+ await body.click()
+ await clearPadContent(page)
+ await page.keyboard.type(padText)
+ // Navigate to the end of the text
+ await page.keyboard.press('End');
+ // Delete the last character
+ await page.keyboard.press('Backspace');
+ const text = await body.locator('div').innerText();
+ expect(text).toBe(padText.slice(0, -1));
+})
diff --git a/src/tests/frontend/specs/alphabet.js b/src/tests/frontend/specs/alphabet.js
deleted file mode 100644
index 999cfdf3a..000000000
--- a/src/tests/frontend/specs/alphabet.js
+++ /dev/null
@@ -1,24 +0,0 @@
-'use strict';
-
-describe('All the alphabet works n stuff', function () {
- const expectedString = 'abcdefghijklmnopqrstuvwxyz';
-
- // create a new pad before each test run
- beforeEach(async function () {
- await helper.aNewPad();
- });
-
- it('when you enter any char it appears right', function (done) {
- const inner$ = helper.padInner$;
-
- // get the first text element out of the inner iframe
- const firstTextElement = inner$('div').first();
-
- // simulate key presses to delete content
- firstTextElement.sendkeys('{selectall}'); // select all
- firstTextElement.sendkeys('{del}'); // clear the first line
- firstTextElement.sendkeys(expectedString); // insert the string
-
- helper.waitFor(() => inner$('div').first().text() === expectedString, 2000).done(done);
- });
-});
diff --git a/src/tests/frontend/specs/bold.js b/src/tests/frontend/specs/bold.js
deleted file mode 100644
index cadfb7a54..000000000
--- a/src/tests/frontend/specs/bold.js
+++ /dev/null
@@ -1,64 +0,0 @@
-'use strict';
-
-describe('bold button', function () {
- // create a new pad before each test run
- beforeEach(async function () {
- await helper.aNewPad();
- });
-
- it('makes text bold on click', function (done) {
- const inner$ = helper.padInner$;
- const chrome$ = helper.padChrome$;
-
- // get the first text element out of the inner iframe
- const $firstTextElement = inner$('div').first();
-
- // select this text element
- $firstTextElement.sendkeys('{selectall}');
-
- // get the bold button and click it
- const $boldButton = chrome$('.buttonicon-bold');
- $boldButton.trigger('click');
-
- const $newFirstTextElement = inner$('div').first();
-
- // is there a element now?
- const isBold = $newFirstTextElement.find('b').length === 1;
-
- // expect it to be bold
- expect(isBold).to.be(true);
-
- // make sure the text hasn't changed
- expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
-
- done();
- });
-
- it('makes text bold on keypress', function (done) {
- const inner$ = helper.padInner$;
-
- // get the first text element out of the inner iframe
- const $firstTextElement = inner$('div').first();
-
- // select this text element
- $firstTextElement.sendkeys('{selectall}');
-
- const e = new inner$.Event(helper.evtType);
- e.ctrlKey = true; // Control key
- e.which = 66; // b
- inner$('#innerdocbody').trigger(e);
-
- const $newFirstTextElement = inner$('div').first();
-
- // is there a element now?
- const isBold = $newFirstTextElement.find('b').length === 1;
-
- // expect it to be bold
- expect(isBold).to.be(true);
-
- // make sure the text hasn't changed
- expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
-
- done();
- });
-});
diff --git a/src/tests/frontend/specs/change_user_name.js b/src/tests/frontend/specs/change_user_name.js
deleted file mode 100644
index b146a1281..000000000
--- a/src/tests/frontend/specs/change_user_name.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-describe('change username value', function () {
- // create a new pad before each test run
- beforeEach(async function () {
- await helper.aNewPad();
- });
-
- it('Remembers the user name after a refresh', async function () {
- this.timeout(10000);
- await helper.toggleUserList();
- await helper.setUserName('😃');
- // Give the server an opportunity to write the new name.
- await new Promise((resolve) => setTimeout(resolve, 1000));
- // get a new pad, but don't clear the cookies
- await helper.aNewPad({clearCookies: false});
- await helper.toggleUserList();
- await helper.waitForPromise(() => helper.usernameField().val() === '😃');
- });
-
- it('Own user name is shown when you enter a chat', async function () {
- this.timeout(10000);
- await helper.toggleUserList();
- await helper.setUserName('😃');
-
- await helper.showChat();
- await helper.sendChatMessage('O hi{enter}');
-
- await helper.waitForPromise(() => {
- // username:hours:minutes text
- const chatText = helper.chatTextParagraphs().text();
- return chatText.indexOf('😃') === 0;
- });
- });
-});
diff --git a/src/tests/frontend/specs/chat.js b/src/tests/frontend/specs/chat.js
deleted file mode 100644
index 82527f372..000000000
--- a/src/tests/frontend/specs/chat.js
+++ /dev/null
@@ -1,116 +0,0 @@
-'use strict';
-
-describe('Chat messages and UI', function () {
- // create a new pad before each test run
- beforeEach(async function () {
- await helper.aNewPad();
- });
-
- it('opens chat, sends a message, makes sure it exists ' +
- 'on the page and hides chat', async function () {
- this.timeout(3000);
- const chatValue = 'JohnMcLear';
-
- await helper.showChat();
- await helper.sendChatMessage(`${chatValue}{enter}`);
-
- expect(helper.chatTextParagraphs().length).to.be(1);
-
- //
- // unnamed:
- // 12:38
- // JohnMcLear
- //
- const username = helper.chatTextParagraphs().children('b').text();
- const time = helper.chatTextParagraphs().children('.time').text();
-
- // TODO: The '\n' is an artifact of $.sendkeys('{enter}'). Figure out how to get rid of it
- // without breaking the other tests that use $.sendkeys().
- expect(helper.chatTextParagraphs().text()).to.be(`${username}${time} ${chatValue}\n`);
-
- await helper.hideChat();
- });
-
- it("makes sure that an empty message can't be sent", async function () {
- const chatValue = 'mluto';
-
- await helper.showChat();
-
- // simulate a keypress of typing enter, mluto and enter (to send 'mluto')
- await helper.sendChatMessage(`{enter}${chatValue}{enter}`);
-
- const chat = helper.chatTextParagraphs();
-
- expect(chat.length).to.be(1);
-
- // check that the received message is not the empty one
- const username = chat.children('b').text();
- const time = chat.children('.time').text();
-
- // TODO: Each '\n' is an artifact of $.sendkeys('{enter}'). Figure out how to get rid of them
- // without breaking the other tests that use $.sendkeys().
- expect(chat.text()).to.be(`${username}${time} \n${chatValue}\n`);
- });
-
- it('makes chat stick to right side of the screen via settings, ' +
- 'remove sticky via settings, close it', async function () {
- this.timeout(5000);
- await helper.showSettings();
-
- await helper.enableStickyChatviaSettings();
- expect(helper.isChatboxShown()).to.be(true);
- expect(helper.isChatboxSticky()).to.be(true);
-
- await helper.disableStickyChatviaSettings();
- expect(helper.isChatboxSticky()).to.be(false);
- expect(helper.isChatboxShown()).to.be(true);
-
- await helper.hideChat();
- expect(helper.isChatboxSticky()).to.be(false);
- expect(helper.isChatboxShown()).to.be(false);
- });
-
- it('makes chat stick to right side of the screen via icon on the top' +
- ' right, remove sticky via icon, close it', async function () {
- this.timeout(5000);
- await helper.showChat();
-
- await helper.enableStickyChatviaIcon();
- expect(helper.isChatboxShown()).to.be(true);
- expect(helper.isChatboxSticky()).to.be(true);
-
- await helper.disableStickyChatviaIcon();
- expect(helper.isChatboxShown()).to.be(true);
- expect(helper.isChatboxSticky()).to.be(false);
-
- await helper.hideChat();
- expect(helper.isChatboxSticky()).to.be(false);
- expect(helper.isChatboxShown()).to.be(false);
- });
-
- xit('Checks showChat=false URL Parameter hides chat then' +
- ' when removed it shows chat', async function () {
- // give it a second to save the username on the server side
- await new Promise((resolve) => setTimeout(resolve, 3000));
-
- // get a new pad, but don't clear the cookies
- await helper.aNewPad({clearCookies: false, params: {showChat: 'false'}});
-
- let chrome$ = helper.padChrome$;
- let chaticon = chrome$('#chaticon');
- // chat should be hidden.
- expect(chaticon.is(':visible')).to.be(false);
-
- // give it a second to save the username on the server side
- await new Promise((resolve) => setTimeout(resolve, 1000));
-
- // get a new pad, but don't clear the cookies
- await helper.aNewPad({clearCookies: false});
-
- chrome$ = helper.padChrome$;
- chaticon = chrome$('#chaticon');
- // chat should be visible.
- expect(chaticon.is(':visible')).to.be(true);
- });
-});
diff --git a/src/tests/frontend/specs/delete.js b/src/tests/frontend/specs/delete.js
deleted file mode 100644
index 05164280b..000000000
--- a/src/tests/frontend/specs/delete.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict';
-
-describe('delete keystroke', function () {
- // create a new pad before each test run
- beforeEach(async function () {
- await helper.aNewPad();
- });
-
- it('makes text delete', async function () {
- const inner$ = helper.padInner$;
-
- // get the first text element out of the inner iframe
- const $firstTextElement = inner$('div').first();
-
- // get the original length of this element
- const elementLength = $firstTextElement.text().length;
-
- // simulate key presses to delete content
- $firstTextElement.sendkeys('{leftarrow}'); // simulate a keypress of the left arrow key
- $firstTextElement.sendkeys('{del}'); // simulate a keypress of delete
-
- const $newFirstTextElement = inner$('div').first();
-
- // get the new length of this element
- const newElementLength = $newFirstTextElement.text().length;
-
- // expect it to be one char less in length
- expect(newElementLength).to.be((elementLength - 1));
- });
-});