Added playwright tests. (#6212)

* Added playwright tests.

* Added clear authorship color.

* Ported enter ts.

* Ported more tests.

* Commented helper tests.

* Fixed admin tests.

* Fixed.

* Fixed admin pages not there.

* Fixed waiting.

* Upload playwright report.

* Remove saucelabs

* Fixed waiting.

* Fixed upload artifact.

* Also install deps.

* Added retry mechanism.

* Added timeout for restart etherpad server.

* Fixed tests.

* Added frontend playwright tests.
This commit is contained in:
SamTV12345 2024-03-10 23:18:50 +01:00 committed by GitHub
parent db46ffb63b
commit c2699e4528
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1568 additions and 1285 deletions

View file

@ -0,0 +1,59 @@
import {expect, test} from "@playwright/test";
import {loginToAdmin, restartEtherpad, saveSettings} from "../helper/adminhelper";
test.beforeEach(async ({ page })=>{
await loginToAdmin(page, 'admin', 'changeme1');
})
test.describe('admin settings',()=> {
test('Are Settings visible, populated, does save work', async ({page}) => {
await page.goto('http://localhost:9001/admin/settings');
await page.waitForSelector('.settings');
const settings = page.locator('.settings');
await expect(settings).not.toBeEmpty();
const settingsVal = await settings.inputValue()
const settingsLength = settingsVal.length
await settings.fill(`/* test */\n${settingsVal}`)
const newValue = await settings.inputValue()
expect(newValue).toContain('/* test */')
expect(newValue.length).toEqual(settingsLength+11)
await saveSettings(page)
// Check if the changes were actually saved
await page.reload()
await page.waitForSelector('.settings');
await expect(settings).not.toBeEmpty();
const newSettings = page.locator('.settings');
const newSettingsVal = await newSettings.inputValue()
expect(newSettingsVal).toContain('/* test */')
// Change back to old settings
await newSettings.fill(settingsVal)
await saveSettings(page)
await page.reload()
await page.waitForSelector('.settings');
await expect(settings).not.toBeEmpty();
const oldSettings = page.locator('.settings');
const oldSettingsVal = await oldSettings.inputValue()
expect(oldSettingsVal).toEqual(settingsVal)
expect(oldSettingsVal.length).toEqual(settingsLength)
})
test('restart works', async function ({page}) {
await page.goto('http://localhost:9001/admin/settings');
await page.waitForSelector('.settings')
await restartEtherpad(page)
await page.waitForSelector('.settings')
const settings = page.locator('.settings');
await expect(settings).not.toBeEmpty();
await page.waitForSelector('.menu')
});
})

View file

@ -0,0 +1,39 @@
import {expect, test} from "@playwright/test";
import {loginToAdmin} from "../helper/adminhelper";
test.beforeEach(async ({ page })=>{
await loginToAdmin(page, 'admin', 'changeme1');
await page.goto('http://localhost:9001/admin/help')
})
test('Shows troubleshooting page manager', async ({page}) => {
await page.goto('http://localhost:9001/admin/help')
await page.waitForSelector('.menu')
const menu = page.locator('.menu');
await expect(menu.locator('li')).toHaveCount(4);
})
test('Shows a version number', async function ({page}) {
await page.goto('http://localhost:9001/admin/help')
await page.waitForSelector('.menu')
const helper = page.locator('.help-block').locator('div').nth(1)
const version = (await helper.textContent())!.split('.');
expect(version.length).toBe(3)
});
test('Lists installed parts', async function ({page}) {
await page.goto('http://localhost:9001/admin/help')
await page.waitForSelector('.menu')
await page.waitForSelector('.innerwrapper ul')
const parts = page.locator('.innerwrapper ul').nth(1);
expect(await parts.textContent()).toContain('ep_etherpad-lite/adminsettings');
});
test('Lists installed hooks', async function ({page}) {
await page.goto('http://localhost:9001/admin/help')
await page.waitForSelector('.menu')
await page.waitForSelector('.innerwrapper ul')
const helper = page.locator('.innerwrapper ul').nth(2);
expect(await helper.textContent()).toContain('express');
});

View file

@ -0,0 +1,111 @@
import {expect, test} from "@playwright/test";
import {loginToAdmin} from "../helper/adminhelper";
test.beforeEach(async ({ page })=>{
await loginToAdmin(page, 'admin', 'changeme1');
await page.goto('http://localhost:9001/admin/plugins')
})
test.describe('Plugins page', ()=> {
test('List some plugins', async ({page}) => {
await page.waitForSelector('.search-field');
const pluginTable = page.locator('table tbody').nth(1);
await expect(pluginTable).not.toBeEmpty()
const plugins = await pluginTable.locator('tr').count()
expect(plugins).toBeGreaterThan(10)
})
test('Searches for a plugin', async ({page}) => {
await page.waitForSelector('.search-field');
await page.click('.search-field')
await page.keyboard.type('ep_font_color3')
await page.keyboard.press('Enter')
const pluginTable = page.locator('table tbody').nth(1);
await expect(pluginTable.locator('tr')).toHaveCount(1)
await expect(pluginTable.locator('tr').first()).toContainText('ep_font_color3')
})
test('Attempt to Install and Uninstall a plugin', async ({page}) => {
await page.waitForSelector('.search-field');
const pluginTable = page.locator('table tbody').nth(1);
await expect(pluginTable).not.toBeEmpty({
timeout: 15000
})
const plugins = await pluginTable.locator('tr').count()
expect(plugins).toBeGreaterThan(10)
// Now everything is loaded, lets install a plugin
await page.click('.search-field')
await page.keyboard.type('ep_font_color3')
await page.keyboard.press('Enter')
await expect(pluginTable.locator('tr')).toHaveCount(1)
const pluginRow = pluginTable.locator('tr').first()
await expect(pluginRow).toContainText('ep_font_color3')
// Select Installation button
await pluginRow.locator('td').nth(4).locator('button').first().click()
await page.waitForTimeout(100)
await page.waitForSelector('table tbody')
const installedPlugins = page.locator('table tbody').first()
const installedPluginsRows = installedPlugins.locator('tr')
await expect(installedPluginsRows).toHaveCount(2, {
timeout: 15000
})
const installedPluginRow = installedPluginsRows.nth(1)
await expect(installedPluginRow).toContainText('ep_font_color3')
await installedPluginRow.locator('td').nth(2).locator('button').first().click()
// Wait for the uninstallation to complete
await expect(installedPluginsRows).toHaveCount(1, {
timeout: 15000
})
})
})
/*
it('Attempt to Update a plugin', async function () {
this.timeout(280000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000);
if (helper.admin$('.ep_align').length === 0) this.skip();
await helper.waitForPromise(
() => helper.admin$('.ep_align .version').text().split('.').length >= 2);
const minorVersionBefore =
parseInt(helper.admin$('.ep_align .version').text().split('.')[1]);
if (!minorVersionBefore) {
throw new Error('Unable to get minor number of plugin, is the plugin installed?');
}
if (minorVersionBefore !== 2) this.skip();
helper.waitForPromise(
() => helper.admin$('.ep_align .do-update').length === 1);
await timeout(500); // HACK! Please submit better fix..
const $doUpdateButton = helper.admin$('.ep_align .do-update');
$doUpdateButton.trigger('click');
// ensure its showing as Updating
await helper.waitForPromise(
() => helper.admin$('.ep_align .message').text() === 'Updating');
// Ensure it's a higher minor version IE 0.3.x as 0.2.x was installed
// Coverage for https://github.com/ether/etherpad-lite/issues/4536
await helper.waitForPromise(() => parseInt(helper.admin$('.ep_align .version')
.text()
.split('.')[1]) > minorVersionBefore, 60000, 1000);
// allow 50 seconds, check every 1 second.
});
*/

View file

@ -0,0 +1,32 @@
import {expect, Page} from "@playwright/test";
export const loginToAdmin = async (page: Page, username: string, password: string) => {
await page.goto('http://localhost:9001/admin/');
await page.waitForSelector('input[name="username"]');
await page.fill('input[name="username"]', username);
await page.fill('input[name="password"]', password);
await page.click('input[type="button"]');
}
export const saveSettings = async (page: Page) => {
// Click save
await page.locator('.settings-button-bar').locator('button').first().click()
await page.waitForSelector('.ToastRootSuccess')
}
export const restartEtherpad = async (page: Page) => {
// Click restart
const restartButton = page.locator('.settings-button-bar').locator('.settingsButton').nth(1)
const settings = page.locator('.settings');
await expect(settings).not.toBeEmpty();
await expect(restartButton).toBeVisible()
await page.locator('.settings-button-bar')
.locator('.settingsButton')
.nth(1)
.click()
await page.waitForTimeout(500)
await page.waitForSelector('.settings')
}

View file

@ -0,0 +1,155 @@
import {Frame, Locator, Page} from "@playwright/test";
import {MapArrayType} from "../../../node/types/MapType";
import {randomInt} from "node:crypto";
export const getPadOuter = async (page: Page): Promise<Frame> => {
return page.frame('ace_outer')!;
}
export const getPadBody = async (page: Page): Promise<Locator> => {
return page.frame('ace_inner')!.locator('#innerdocbody')
}
export const selectAllText = async (page: Page) => {
await page.keyboard.down('Control');
await page.keyboard.press('A');
await page.keyboard.up('Control');
}
export const toggleUserList = async (page: Page) => {
await page.locator("button[data-l10n-id='pad.toolbar.showusers.title']").click()
}
export const setUserName = async (page: Page, userName: string) => {
await page.waitForSelector('[class="popup popup-show"]')
await page.click("input[data-l10n-id='pad.userlist.entername']");
await page.keyboard.type(userName);
}
export const showChat = async (page: Page) => {
const chatIcon = page.locator("#chaticon")
const classes = await chatIcon.getAttribute('class')
if (classes && !classes.includes('visible')) return
await chatIcon.click()
await page.waitForFunction(`!document.querySelector('#chaticon').classList.contains('visible')`)
}
export const getCurrentChatMessageCount = async (page: Page) => {
return await page.locator('#chattext').locator('p').count()
}
export const getChatUserName = async (page: Page) => {
return await page.locator('#chattext')
.locator('p')
.locator('b')
.innerText()
}
export const getChatMessage = async (page: Page) => {
return (await page.locator('#chattext')
.locator('p')
.textContent({}))!
.split(await getChatTime(page))[1]
}
export const getChatTime = async (page: Page) => {
return await page.locator('#chattext')
.locator('p')
.locator('.time')
.innerText()
}
export const sendChatMessage = async (page: Page, message: string) => {
let currentChatCount = await getCurrentChatMessageCount(page)
const chatInput = page.locator('#chatinput')
await chatInput.click()
await page.keyboard.type(message)
await page.keyboard.press('Enter')
if(message === "") return
await page.waitForFunction(`document.querySelector('#chattext').querySelectorAll('p').length >${currentChatCount}`)
}
export const isChatBoxShown = async (page: Page):Promise<boolean> => {
const classes = await page.locator('#chatbox').getAttribute('class')
return classes !==null && classes.includes('visible')
}
export const isChatBoxSticky = async (page: Page):Promise<boolean> => {
const classes = await page.locator('#chatbox').getAttribute('class')
console.log('Chat', classes && classes.includes('stickyChat'))
return classes !==null && classes.includes('stickyChat')
}
export const hideChat = async (page: Page) => {
if(!await isChatBoxShown(page)|| await isChatBoxSticky(page)) return
await page.locator('#titlecross').click()
await page.waitForFunction(`!document.querySelector('#chatbox').classList.contains('stickyChat')`)
}
export const enableStickyChatviaIcon = async (page: Page) => {
if(await isChatBoxSticky(page)) return
await page.locator('#titlesticky').click()
await page.waitForFunction(`document.querySelector('#chatbox').classList.contains('stickyChat')`)
}
export const disableStickyChatviaIcon = async (page: Page) => {
if(!await isChatBoxSticky(page)) return
await page.locator('#titlecross').click()
await page.waitForFunction(`!document.querySelector('#chatbox').classList.contains('stickyChat')`)
}
export const appendQueryParams = async (page: Page, queryParameters: MapArrayType<string>) => {
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
const padId = "FRONTEND_TESTS"+randomInt(0, 1000);
await page.goto('http://localhost:9001/p/'+padId);
await page.waitForSelector('iframe[name="ace_outer"]');
return padId;
}
export const goToPad = async (page: Page, padId: string) => {
await page.goto('http://localhost:9001/p/'+padId);
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');
}
export const writeToPad = async (page: Page, text: string) => {
const body = await getPadBody(page);
await body.click();
await page.keyboard.type(text);
}
export const clearAuthorship = async (page: Page) => {
await page.locator("button[data-l10n-id='pad.toolbar.clearAuthorship.title']").click()
}
export const undoChanges = async (page: Page) => {
await page.keyboard.down('Control');
await page.keyboard.press('z');
await page.keyboard.up('Control');
}
export const pressUndoButton = async (page: Page) => {
await page.locator('.buttonicon-undo').click()
}

View file

@ -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)')
}

View file

@ -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);
});
});

View file

@ -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');
})
})

View file

@ -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)
});

View file

@ -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)
});

View file

@ -0,0 +1,87 @@
import {expect, test} from "@playwright/test";
import {
clearAuthorship,
clearPadContent,
getPadBody,
goToNewPad, pressUndoButton,
selectAllText,
undoChanges,
writeToPad
} from "../helper/padHelper";
test.beforeEach(async ({ page })=>{
// create a new pad before each test run
await goToNewPad(page);
})
test('clear authorship color', async ({page}) => {
// get the inner iframe
const innerFrame = await getPadBody(page);
const padText = "Hello"
// type some text
await clearPadContent(page);
await writeToPad(page, padText);
const retrievedClasses = await innerFrame.locator('div span').nth(0).getAttribute('class')
expect(retrievedClasses).toContain('author');
// select the text
await innerFrame.click()
await selectAllText(page);
await clearAuthorship(page);
// does the first div include an author class?
const firstDivClass = await innerFrame.locator('div').nth(0).getAttribute('class');
expect(firstDivClass).not.toContain('author');
const classes = page.locator('div.disconnected')
expect(await classes.isVisible()).toBe(false)
})
test("makes text clear authorship colors and checks it can't be undone", async function ({page}) {
const innnerPad = await getPadBody(page);
const padText = "Hello"
// type some text
await clearPadContent(page);
await writeToPad(page, padText);
// get the first text element out of the inner iframe
const firstDivClass = innnerPad.locator('div').nth(0)
const retrievedClasses = await innnerPad.locator('div span').nth(0).getAttribute('class')
expect(retrievedClasses).toContain('author');
await firstDivClass.focus()
await clearAuthorship(page);
expect(await firstDivClass.getAttribute('class')).not.toContain('author');
await undoChanges(page);
const changedFirstDiv = innnerPad.locator('div').nth(0)
expect(await changedFirstDiv.getAttribute('class')).not.toContain('author');
await pressUndoButton(page);
const secondChangedFirstDiv = innnerPad.locator('div').nth(0)
expect(await secondChangedFirstDiv.getAttribute('class')).not.toContain('author');
});
// Test for https://github.com/ether/etherpad-lite/issues/5128
test('clears authorship when first line has line attributes', async function ({page}) {
// Make sure there is text with author info. The first line must have a line attribute.
const padBody = await getPadBody(page);
await padBody.click()
await clearPadContent(page);
await writeToPad(page,'Hello')
await page.locator('.buttonicon-insertunorderedlist').click();
const retrievedClasses = await padBody.locator('div span').nth(0).getAttribute('class')
expect(retrievedClasses).toContain('author');
await padBody.click()
await selectAllText(page);
await clearAuthorship(page);
const retrievedClasses2 = await padBody.locator('div span').nth(0).getAttribute('class')
expect(retrievedClasses2).not.toContain('author');
expect(await page.locator('[class*="author-"]').count()).toBe(0)
});

View file

@ -0,0 +1,94 @@
import {clearPadContent, getPadBody, goToNewPad, goToPad, writeToPad} from "../helper/padHelper";
import {expect, Page, test} from "@playwright/test";
let padId = "";
test.beforeEach(async ({ page })=>{
// create a new pad before each test run
padId = await goToNewPad(page);
const body = await getPadBody(page);
await body.click();
await clearPadContent(page);
await writeToPad(page, "Hello World");
await page.keyboard.press('Enter');
await writeToPad(page, "Hello World");
await page.keyboard.press('Enter');
await writeToPad(page, "Hello World");
await page.keyboard.press('Enter');
await writeToPad(page, "Hello World");
await page.keyboard.press('Enter');
await writeToPad(page, "Hello World");
await page.keyboard.press('Enter');
})
test.describe('Messages in the COLLABROOM', function () {
const user1Text = 'text created by user 1';
const user2Text = 'text created by user 2';
const replaceLineText = async (lineNumber: number, newText: string, page: Page) => {
const body = await getPadBody(page)
const div = body.locator('div').nth(lineNumber)
// simulate key presses to delete content
await div.locator('span').selectText() // select all
await page.keyboard.press('Backspace') // clear the first line
await page.keyboard.type(newText) // insert the string
};
test('bug #4978 regression test', async function ({browser}) {
// The bug was triggered by receiving a change from another user while simultaneously composing
// a character and waiting for an acknowledgement of a previously sent change.
// User 1
const context1 = await browser.newContext();
const page1 = await context1.newPage();
await goToPad(page1, padId)
const body1 = await getPadBody(page1)
// Perform actions as User 1...
// User 2
const context2 = await browser.newContext();
const page2 = await context2.newPage();
await goToPad(page2, padId)
const body2 = await getPadBody(page1)
await replaceLineText(0, user1Text,page1);
const text = await body2.locator('div').nth(0).textContent()
const res = text === user1Text
expect(res).toBe(true)
// User 1 starts a character composition.
await replaceLineText(1, user2Text, page2)
await expect(body1.locator('div').nth(1)).toHaveText(user2Text)
// Users 1 and 2 make some more changes.
await replaceLineText(3, user2Text, page2);
await expect(body1.locator('div').nth(3)).toHaveText(user2Text)
await replaceLineText(2, user1Text, page1);
await expect(body2.locator('div').nth(2)).toHaveText(user1Text)
// All changes should appear in both views.
const expectedLines = [
user1Text,
user2Text,
user1Text,
user2Text,
];
for (let i=0;i<expectedLines.length;i++){
expect(await body1.locator('div').nth(i).textContent()).toBe(expectedLines[i]);
}
for (let i=0;i<expectedLines.length;i++){
expect(await body2.locator('div').nth(i).textContent()).toBe(expectedLines[i]);
}
});
});

View file

@ -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));
})

View file

@ -0,0 +1,146 @@
import {expect, Page, test} from "@playwright/test";
import {goToNewPad} from "../helper/padHelper";
test.beforeEach(async ({ page })=>{
// create a new pad before each test run
await goToNewPad(page);
})
test.describe('embed links', function () {
const objectify = function (str: string) {
const hash = {};
const parts = str.split('&');
for (let i = 0; i < parts.length; i++) {
const keyValue = parts[i].split('=');
// @ts-ignore
hash[keyValue[0]] = keyValue[1];
}
return hash;
};
const checkiFrameCode = async function (embedCode: string, readonly: boolean, page: Page) {
// turn the code into an html element
await page.setContent(embedCode, {waitUntil: 'load'})
const locator = page.locator('body').locator('iframe').last()
// read and check the frame attributes
const width = await locator.getAttribute('width');
const height = await locator.getAttribute('height');
const name = await locator.getAttribute('name');
expect(width).toBe('100%');
expect(height).toBe('600');
expect(name).toBe(readonly ? 'embed_readonly' : 'embed_readwrite');
// parse the url
const src = (await locator.getAttribute('src'))!;
const questionMark = src.indexOf('?');
const url = src.substring(0, questionMark);
const paramsStr = src.substring(questionMark + 1);
const params = objectify(paramsStr);
const expectedParams = {
showControls: 'true',
showChat: 'true',
showLineNumbers: 'true',
useMonospaceFont: 'false',
};
// check the url
if (readonly) {
expect(url.indexOf('r.') > 0).toBe(true);
} else {
expect(url).toBe(await page.evaluate(() => window.location.href));
}
// check if all parts of the url are like expected
expect(params).toEqual(expectedParams);
};
test.describe('read and write', function () {
test.beforeEach(async ({ page })=>{
// create a new pad before each test run
await goToNewPad(page);
})
test.describe('the share link', function () {
test('is the actual pad url', async function ({page}) {
const shareButton = page.locator('.buttonicon-embed')
// open share dropdown
await shareButton.click()
// get the link of the share field + the actual pad url and compare them
const shareLink = await page.locator('#linkinput').inputValue()
const padURL = page.url();
expect(shareLink).toBe(padURL);
});
});
test.describe('the embed as iframe code', function () {
test('is an iframe with the the correct url parameters and correct size', async function ({page}) {
const shareButton = page.locator('.buttonicon-embed')
await shareButton.click()
// get the link of the share field + the actual pad url and compare them
const embedCode = await page.locator('#embedinput').inputValue()
await checkiFrameCode(embedCode, false, page);
});
});
});
test.describe('when read only option is set', function () {
test.beforeEach(async ({ page })=>{
// create a new pad before each test run
await goToNewPad(page);
})
test.describe('the share link', function () {
test('shows a read only url', async function ({page}) {
// open share dropdown
const shareButton = page.locator('.buttonicon-embed')
await shareButton.click()
const readonlyCheckbox = page.locator('#readonlyinput')
await readonlyCheckbox.click({
force: true
})
await page.waitForSelector('#readonlyinput:checked')
// get the link of the share field + the actual pad url and compare them
const shareLink = await page.locator('#linkinput').inputValue()
const containsReadOnlyLink = shareLink.indexOf('r.') > 0;
expect(containsReadOnlyLink).toBe(true);
});
});
test.describe('the embed as iframe code', function () {
test('is an iframe with the the correct url parameters and correct size', async function ({page}) {
// open share dropdown
const shareButton = page.locator('.buttonicon-embed')
await shareButton.click()
// check read only checkbox, a bit hacky
const readonlyCheckbox = page.locator('#readonlyinput')
await readonlyCheckbox.click({
force: true
})
await page.waitForSelector('#readonlyinput:checked')
// get the link of the share field + the actual pad url and compare them
const embedCode = await page.locator('#embedinput').inputValue()
await checkiFrameCode(embedCode, true, page);
});
});
})
})

View file

@ -0,0 +1,63 @@
'use strict';
import {expect, test} from "@playwright/test";
import {getPadBody, goToNewPad, writeToPad} from "../helper/padHelper";
test.beforeEach(async ({ page })=>{
await goToNewPad(page);
})
test.describe('enter keystroke', function () {
test('creates a new line & puts cursor onto a new line', async function ({page}) {
const padBody = await getPadBody(page);
// get the first text element out of the inner iframe
const firstTextElement = padBody.locator('div').nth(0)
// get the original string value minus the last char
const originalTextValue = await firstTextElement.textContent();
// simulate key presses to enter content
await firstTextElement.click()
await page.keyboard.press('Home');
await page.keyboard.press('Enter');
const updatedFirstElement = padBody.locator('div').nth(0)
expect(await updatedFirstElement.textContent()).toBe('')
const newSecondLine = padBody.locator('div').nth(1);
// expect the second line to be the same as the original first line.
expect(await newSecondLine.textContent()).toBe(originalTextValue);
});
test('enter is always visible after event', async function ({page}) {
const padBody = await getPadBody(page);
const originalLength = await padBody.locator('div').count();
let lastLine = padBody.locator('div').last();
// simulate key presses to enter content
let i = 0;
const numberOfLines = 15;
while (i < numberOfLines) {
lastLine = padBody.locator('div').last();
await lastLine.focus();
await page.keyboard.press('End');
await page.keyboard.press('Enter');
// check we can see the caret..
i++;
}
expect(await padBody.locator('div').count()).toBe(numberOfLines + originalLength);
// is edited line fully visible?
const lastDiv = padBody.locator('div').last()
const lastDivOffset = await lastDiv.boundingBox();
const bottomOfLastLine = lastDivOffset!.y + lastDivOffset!.height;
const scrolledWindow = page.frames()[0];
const windowOffset = await scrolledWindow.evaluate(() => window.pageYOffset);
const windowHeight = await scrolledWindow.evaluate(() => window.innerHeight);
expect(windowOffset + windowHeight).toBeGreaterThan(bottomOfLastLine);
});
});

View file

@ -0,0 +1,241 @@
import {expect, test} from "@playwright/test";
import {clearPadContent, getPadBody, goToNewPad, writeToPad} from "../helper/padHelper";
test.beforeEach(async ({ page })=>{
await goToNewPad(page);
})
test.describe('indentation button', function () {
test('indent text with keypress', async function ({page}) {
const padBody = await getPadBody(page);
// get the first text element out of the inner iframe
const $firstTextElement = padBody.locator('div').first();
// select this text element
await $firstTextElement.selectText()
await page.keyboard.press('Tab');
const uls = padBody.locator('div').first().locator('ul li')
await expect(uls).toHaveCount(1);
});
test('indent text with button', async function ({page}) {
const padBody = await getPadBody(page);
await page.locator('.buttonicon-indent').click()
const uls = padBody.locator('div').first().locator('ul li')
await expect(uls).toHaveCount(1);
});
test('keeps the indent on enter for the new line', async function ({page}) {
const padBody = await getPadBody(page);
await padBody.click()
await clearPadContent(page)
await page.locator('.buttonicon-indent').click()
// type a bit, make a line break and type again
await padBody.locator('div').first().focus()
await page.keyboard.type('line 1')
await page.keyboard.press('Enter');
await page.keyboard.type('line 2')
await page.keyboard.press('Enter');
const $newSecondLine = padBody.locator('div span').nth(1)
const hasULElement = padBody.locator('ul li')
await expect(hasULElement).toHaveCount(3);
await expect($newSecondLine).toHaveText('line 2');
});
test('indents text with spaces on enter if previous line ends ' +
"with ':', '[', '(', or '{'", async function ({page}) {
const padBody = await getPadBody(page);
await padBody.click()
await clearPadContent(page)
// type a bit, make a line break and type again
const $firstTextElement = padBody.locator('div').first();
await writeToPad(page, "line with ':'");
await page.keyboard.press('Enter');
await writeToPad(page, "line with '['");
await page.keyboard.press('Enter');
await writeToPad(page, "line with '('");
await page.keyboard.press('Enter');
await writeToPad(page, "line with '{{}'");
await expect(padBody.locator('div').nth(3)).toHaveText("line with '{{}'");
// we validate bottom to top for easier implementation
// curly braces
const $lineWithCurlyBraces = padBody.locator('div').nth(3)
await $lineWithCurlyBraces.click();
await page.keyboard.press('End');
await page.keyboard.type('{{');
// cannot use sendkeys('{enter}') here, browser does not read the command properly
await page.keyboard.press('Enter');
expect(await padBody.locator('div').nth(4).textContent()).toMatch(/\s{4}/); // tab === 4 spaces
// parenthesis
const $lineWithParenthesis = padBody.locator('div').nth(2)
await $lineWithParenthesis.click();
await page.keyboard.press('End');
await page.keyboard.type('(');
await page.keyboard.press('Enter');
const $lineAfterParenthesis = padBody.locator('div').nth(3)
expect(await $lineAfterParenthesis.textContent()).toMatch(/\s{4}/);
// bracket
const $lineWithBracket = padBody.locator('div').nth(1)
await $lineWithBracket.click();
await page.keyboard.press('End');
await page.keyboard.type('[');
await page.keyboard.press('Enter');
const $lineAfterBracket = padBody.locator('div').nth(2);
expect(await $lineAfterBracket.textContent()).toMatch(/\s{4}/);
// colon
const $lineWithColon = padBody.locator('div').first();
await $lineWithColon.click();
await page.keyboard.press('End');
await page.keyboard.type(':');
await page.keyboard.press('Enter');
const $lineAfterColon = padBody.locator('div').nth(1);
expect(await $lineAfterColon.textContent()).toMatch(/\s{4}/);
});
test('appends indentation to the indent of previous line if previous line ends ' +
"with ':', '[', '(', or '{'", async function ({page}) {
const padBody = await getPadBody(page);
await padBody.click()
await clearPadContent(page)
// type a bit, make a line break and type again
await writeToPad(page, " line with some indentation and ':'")
await page.keyboard.press('Enter');
await writeToPad(page, "line 2")
const $lineWithColon = padBody.locator('div').first();
await $lineWithColon.click();
await page.keyboard.press('End');
await page.keyboard.type(':');
await page.keyboard.press('Enter');
const $lineAfterColon = padBody.locator('div').nth(1);
// previous line indentation + regular tab (4 spaces)
expect(await $lineAfterColon.textContent()).toMatch(/\s{6}/);
});
test("issue #2772 shows '*' when multiple indented lines " +
' receive a style and are outdented', async function ({page}) {
const padBody = await getPadBody(page);
await padBody.click()
await clearPadContent(page)
const inner = padBody.locator('div').first();
// make sure pad has more than one line
await inner.click()
await page.keyboard.type('First');
await page.keyboard.press('Enter');
await page.keyboard.type('Second');
// indent first 2 lines
await padBody.locator('div').nth(0).selectText();
await page.locator('.buttonicon-indent').click()
await padBody.locator('div').nth(1).selectText();
await page.locator('.buttonicon-indent').click()
await expect(padBody.locator('ul li')).toHaveCount(2);
// apply bold
await padBody.locator('div').nth(0).selectText();
await page.locator('.buttonicon-bold').click()
await padBody.locator('div').nth(1).selectText();
await page.locator('.buttonicon-bold').click()
await expect(padBody.locator('div b')).toHaveCount(2);
// outdent first 2 lines
await padBody.locator('div').nth(0).selectText();
await page.locator('.buttonicon-outdent').click()
await padBody.locator('div').nth(1).selectText();
await page.locator('.buttonicon-outdent').click()
await expect(padBody.locator('ul li')).toHaveCount(0);
// check if '*' is displayed
const secondLine = padBody.locator('div').nth(1);
await expect(secondLine).toHaveText('Second');
});
test('makes text indented and outdented', async function ({page}) {
// get the inner iframe
const padBody = await getPadBody(page);
// get the first text element out of the inner iframe
let firstTextElement = padBody.locator('div').first();
// select this text element
await firstTextElement.selectText()
// get the indentation button and click it
await page.locator('.buttonicon-indent').click()
let newFirstTextElement = padBody.locator('div').first();
// is there a list-indent class element now?
await expect(newFirstTextElement.locator('ul')).toHaveCount(1);
await expect(newFirstTextElement.locator('li')).toHaveCount(1);
// indent again
await page.locator('.buttonicon-indent').click()
newFirstTextElement = padBody.locator('div').first();
// is there a list-indent class element now?
const ulList = newFirstTextElement.locator('ul').first()
await expect(ulList).toHaveCount(1);
// expect it to be part of a list
expect(await ulList.getAttribute('class')).toBe('list-indent2');
// make sure the text hasn't changed
expect(await newFirstTextElement.textContent()).toBe(await firstTextElement.textContent());
// test outdent
// get the unindentation button and click it twice
newFirstTextElement = padBody.locator('div').first();
await newFirstTextElement.selectText()
await page.locator('.buttonicon-outdent').click()
await page.locator('.buttonicon-outdent').click()
newFirstTextElement = padBody.locator('div').first();
// is there a list-indent class element now?
await expect(newFirstTextElement.locator('ul')).toHaveCount(0);
// make sure the text hasn't changed
expect(await newFirstTextElement.textContent()).toEqual(await firstTextElement.textContent());
});
});