Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Playwright tests #337

Merged
merged 48 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
669ffd0
setup playwright
BrtqKr Apr 15, 2024
9351ef7
setup multiple browsers
BrtqKr Apr 15, 2024
6cfb39e
fix style tests, fix multiple browsers setup
BrtqKr Apr 17, 2024
dcf45fd
add paste test
BrtqKr Apr 18, 2024
850215d
cleanup
BrtqKr Apr 18, 2024
12ac98d
add paste replace and quick type
BrtqKr Apr 18, 2024
c9fc4b8
fix paste permissions in tests
BrtqKr Apr 18, 2024
72f12ef
cut content wip
BrtqKr Apr 19, 2024
8ef9df1
add cut content test
BrtqKr Apr 22, 2024
b2452bb
fix test permissions
BrtqKr Apr 22, 2024
906b98b
add undo test, add debounce value to constants
BrtqKr Apr 23, 2024
abbaff1
redo wip
BrtqKr Apr 23, 2024
f661556
finish redo
BrtqKr Apr 23, 2024
bb58135
cleanup commands
BrtqKr Apr 23, 2024
67a2e9d
cleanup tests structure
BrtqKr Apr 23, 2024
124d151
add blockquote style
BrtqKr Apr 24, 2024
690ddc1
fix blockquote test for firefox
BrtqKr Apr 24, 2024
ac548f7
Merge remote-tracking branch 'origin/main' into brtqkr/add-playwright…
BrtqKr Apr 28, 2024
1dd1977
cleanup
BrtqKr Apr 28, 2024
e26e01f
cleanup
BrtqKr Apr 28, 2024
afa5758
add workflow for e2e test
BrtqKr May 6, 2024
9275353
cleanup, rename constants
BrtqKr May 6, 2024
4286e8e
review fixes
BrtqKr May 6, 2024
9d63b1c
Merge remote-tracking branch 'origin/main' into brtqkr/add-playwright…
BrtqKr May 6, 2024
d0e36b7
update workflow
BrtqKr May 6, 2024
721b2ca
update workflow
BrtqKr May 6, 2024
f12b729
fix home variable
BrtqKr May 6, 2024
e92e31b
tests/textManipulation.spec.ts fix attempt
BrtqKr May 6, 2024
9e1464f
trigger playwright from node modules path
BrtqKr May 6, 2024
7e28ce8
change working dir
BrtqKr May 6, 2024
a702f31
revert to image
BrtqKr May 6, 2024
94e22d6
cleanup
BrtqKr May 6, 2024
9f4d9c3
cleanup
BrtqKr May 6, 2024
3a065f2
cleanup
BrtqKr May 6, 2024
5b06cd2
cleanup
BrtqKr May 6, 2024
7e3e85a
revert to image
BrtqKr May 6, 2024
4daf5aa
cleanup
BrtqKr May 7, 2024
085d094
cleanup
BrtqKr May 9, 2024
abc897a
add ci disable for tests failing with ubuntu-runner
BrtqKr May 9, 2024
94d7ddb
add ci disable for tests failing with ubuntu-runner
BrtqKr May 9, 2024
2764945
fix jest test paths
BrtqKr May 9, 2024
c44a707
review fixes wip
BrtqKr May 13, 2024
a0a1bf2
review fixes wip
BrtqKr May 13, 2024
72e700f
remove constants
BrtqKr May 14, 2024
cd88a46
cleanup
BrtqKr May 14, 2024
b04d1f1
lint
BrtqKr May 14, 2024
3c6d829
fix test paths
BrtqKr May 14, 2024
d612c93
Merge remote-tracking branch 'origin/main' into brtqkr/add-playwright…
BrtqKr Jun 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: E2E Test
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: E2E Test
name: Test web E2E

on:
pull_request:
paths:
- .github/workflows/e2e-test.yml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename the file and change this line

Suggested change
- .github/workflows/e2e-test.yml
- .github/workflows/web-e2e-test.yml

- src/**
- WebExample/**
merge_group:
branches:
- main
push:
branches:
- main
paths:
- .github/workflows/e2e-test.yml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

- src/**
- WebExample/**

jobs:
e2e_test:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
e2e_test:
test:

if: github.repository == 'Expensify/react-native-live-markdown'
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./WebExample

concurrency:
group: e2e-test-${{ github.ref }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

cancel-in-progress: true
steps:
- name: Check out Git repository
uses: actions/checkout@v4

- name: Use Node.js 18
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install node_modules
run: yarn install --immutable

- name: Install browsers
run: npx playwright install --with-deps

- name: Install dependencies for browsers
run: npx playwright install-deps

- name: Run playwright tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: Run playwright tests
- name: Run Playwright tests

run: yarn test
4 changes: 4 additions & 0 deletions WebExample/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ yarn-error.*

# typescript
*.tsbuildinfo
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
5 changes: 4 additions & 1 deletion WebExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
"web": "expo start --web",
"test": "npx playwright test"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"test": "npx playwright test"
"test": "playwright test"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this would work

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please try

},
"dependencies": {
"@expo/webpack-config": "~19.0.1",
Expand All @@ -20,6 +21,8 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@playwright/test": "^1.43.1",
"@types/node": "^20.12.7",
"@types/react": "~18.2.45",
"typescript": "^5.1.3"
},
Expand Down
29 changes: 29 additions & 0 deletions WebExample/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {defineConfig, devices} from '@playwright/test';
import * as TEST_CONST from '../testConstants';

export default defineConfig({
testDir: './tests',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we rename the directory to __tests__?

preserveOutput: 'never',
outputDir: undefined,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to set this prop? What's the default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the default is <package.json-directory>/test-results, as long as we don't use reports I don't see any point in using it + if i remember correctly there was an issue in CI when trying to preserve artifacts from tests.

webServer: {
command: 'yarn web',
url: TEST_CONST.LOCAL_URL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
stderr: 'pipe',
},
projects: [
{
name: 'Chromium',
use: {...devices['Desktop Chrome']},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just devices['Desktop Chrome']?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is directly from the documentation
https://playwright.dev/docs/test-projects

},
{
name: 'Firefox',
use: {...devices['Desktop Firefox']},
},
{
name: 'Webkit',
use: {...devices['Desktop Safari']},
},
],
});
6 changes: 6 additions & 0 deletions WebExample/tests/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
'rules': {
'@lwc/lwc/no-async-await': 'off',
'rulesdir/prefer-import-module-contents': 'off',
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing trailing newline

31 changes: 31 additions & 0 deletions WebExample/tests/input.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {test, expect} from '@playwright/test';
import * as TEST_CONST from '../../testConstants';
import {checkCursorPosition, setupInput} from './utils';

test.beforeEach(async ({page}) => {
await page.goto(TEST_CONST.LOCAL_URL, {waitUntil: 'load'});
});

test.describe('standard input behaviour', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test.describe('standard input behaviour', () => {
test.describe('typing', () => {

test('standard input results', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('standard input results', async ({page}) => {
test('short text', async ({page}) => {

const inputLocator = await setupInput(page, 'clear');

await inputLocator.pressSequentially(TEST_CONST.EXAMPLE_CONTENT);
const value = await inputLocator.innerText();
expect(value).toEqual(TEST_CONST.EXAMPLE_CONTENT);
});

test('fast type cursor position', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('fast type cursor position', async ({page}) => {
test('long text, async ({page}) => {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced - it doesn't describe this test properly. The main purpose was to verify if the cursor position would get desynchronized when typing fast (there was a bug with this behavior). It's not a standard long text input scenario.

const EXAMPLE_LONG_CONTENT = TEST_CONST.EXAMPLE_CONTENT.repeat(3);

const inputLocator = await setupInput(page, 'clear');

await inputLocator.pressSequentially(EXAMPLE_LONG_CONTENT);

expect(await inputLocator.innerText()).toBe(EXAMPLE_LONG_CONTENT);

const cursorPosition = await page.evaluate(checkCursorPosition);

expect(cursorPosition).toBe(EXAMPLE_LONG_CONTENT.length);
});
});
66 changes: 66 additions & 0 deletions WebExample/tests/styles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {test, expect} from '@playwright/test';
import type {Page} from '@playwright/test';
import * as TEST_CONST from '../../testConstants';
import {setupInput} from './utils';

const testMarkdownContentStyle = async ({styleName, style, page}: {styleName: string; style: string; page: Page}) => {
const inputLocator = await setupInput(page);

const elementHandle = inputLocator.locator('span', {hasText: styleName}).last();

let elementStyle;

if (elementHandle) {
await elementHandle.waitFor({state: 'attached'});

elementStyle = await elementHandle.getAttribute('style');
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to move it to an utility function? Looks like we use it again in paste content test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k, I'll move it

expect(elementStyle).toEqual(style);
};

test.beforeEach(async ({page}) => {
await page.goto(TEST_CONST.LOCAL_URL, {waitUntil: 'load'});
await page.click('[data-testid="reset"]');
});

test.describe('markdown content styling', () => {
test('bold', async ({page}) => {
await testMarkdownContentStyle({styleName: 'bold', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.bold.style, page});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of TEST_CONST.MARKDOWN_STYLE_DEFINITIONS. There's nothing wrong with tests containing string literals in multiple copies.

This test should contain "Hello *world*!" string as well as fontWeight: 'bold' somewhere inside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in terms of "Hello *world*!" - sure, but I think having styles defined in a constant is the right way to handle this, If you try checking style definition in multiple cases and/or you are using this styling in a library itself, this might get desynchronized, if someone decides to change it at some point

});

test('link', async ({page}) => {
await testMarkdownContentStyle({styleName: 'link', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.link.style, page});
});

test('title', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure that the names of the tests are aligned with the rest of the codebase

Suggested change
test('title', async ({page}) => {
test('h1', async ({page}) => {

await testMarkdownContentStyle({styleName: 'title', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.title.style, page});
});

test('code', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('code', async ({page}) => {
test('inline code', async ({page}) => {

await testMarkdownContentStyle({styleName: 'code', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.code.style, page});
});

test('codeBlock', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('codeBlock', async ({page}) => {
test('codeblock', async ({page}) => {

await testMarkdownContentStyle({styleName: 'codeBlock', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.codeBlock.style, page});
});

test('hereMention', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('hereMention', async ({page}) => {
test('mention-here', async ({page}) => {

await testMarkdownContentStyle({styleName: 'here', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.here.style, page});
});

test('mentionUser', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('mentionUser', async ({page}) => {
test('mention-user', async ({page}) => {

await testMarkdownContentStyle({styleName: 'mentionUser', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.mentionUser.style, page});
});

test('roomMention', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('roomMention', async ({page}) => {
test('mention-report', async ({page}) => {

await testMarkdownContentStyle({styleName: 'roomMention', style: TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.roomMention.style, page});
});

test('blockquote', async ({page, browserName}) => {
const blockquoteStyle = TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.blockquote.style;
// Firefox border properties are serialized slightly differently
const browserStyle = browserName === 'firefox' ? blockquoteStyle.replace(' border-left-style: solid', ' border-left: 6px solid gray') : blockquoteStyle;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw

Suggested change
const browserStyle = browserName === 'firefox' ? blockquoteStyle.replace(' border-left-style: solid', ' border-left: 6px solid gray') : blockquoteStyle;
const browserStyle = browserName === 'firefox' ? blockquoteStyle.replace('border-left-style: solid', 'border-left: 6px solid gray') : blockquoteStyle;


await testMarkdownContentStyle({styleName: 'blockquote', style: browserStyle, page});
});
});
139 changes: 139 additions & 0 deletions WebExample/tests/textManipulation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {test, expect} from '@playwright/test';
import type {Locator, Page} from '@playwright/test';
import * as TEST_CONST from '../../testConstants';
import {checkCursorPosition, setupInput} from './utils';

const OPERATION_MODIFIER = process.platform === 'darwin' ? 'Meta' : 'Control';

const pasteContent = async ({text, page, inputLocator}: {text: string; page: Page; inputLocator: Locator}) => {
await page.evaluate(async (pasteText) => navigator.clipboard.writeText(pasteText), text);
await inputLocator.focus();
await inputLocator.press(`${OPERATION_MODIFIER}+v`);
};

test.beforeEach(async ({page, context, browserName}) => {
await page.goto(TEST_CONST.LOCAL_URL, {waitUntil: 'load'});
if (browserName === 'chromium') await context.grantPermissions(['clipboard-write', 'clipboard-read']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use curly braces for conditionals everywhere. I believe we should have curly rule enabled everywhere, can you please confirm it?

Suggested change
if (browserName === 'chromium') await context.grantPermissions(['clipboard-write', 'clipboard-read']);
if (browserName === 'chromium') {
await context.grantPermissions(['clipboard-write', 'clipboard-read']);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it wasn't enabled, i've added it

});

test.describe('paste content', () => {
test.skip(({browserName}) => !!process.env.CI && browserName === 'webkit', 'Excluded from webkit CI tests');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test.skip(({browserName}) => !!process.env.CI && browserName === 'webkit', 'Excluded from webkit CI tests');
test.skip(({browserName}) => !!process.env.CI && browserName === 'webkit', 'Excluded from WebKit CI tests');


test('paste', async ({page}) => {
const PASTE_TEXT = 'bold';
const boldStyleDefinition = TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.bold;

const inputLocator = await setupInput(page, 'clear');

const wrappedText = boldStyleDefinition.wrapContent(PASTE_TEXT);
await pasteContent({text: wrappedText, page, inputLocator});

const elementHandle = await inputLocator.locator('span', {hasText: PASTE_TEXT}).last();
let elementStyle;
if (elementHandle) {
await elementHandle.waitFor({state: 'attached'});

elementStyle = await elementHandle.getAttribute('style');
}
expect(elementStyle).toEqual(boldStyleDefinition.style);
});

test('paste replace', async ({page}) => {
const inputLocator = await setupInput(page, 'reset');

await inputLocator.focus();
await inputLocator.press(`${OPERATION_MODIFIER}+a`);

const newText = '*bold*';
await pasteContent({text: newText, page, inputLocator});

expect(await inputLocator.innerText()).toBe(newText);
});

test('paste undo', async ({page, browserName}) => {
test.skip(!!process.env.CI && browserName === 'firefox', 'Excluded from firefox CI tests');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test.skip(!!process.env.CI && browserName === 'firefox', 'Excluded from firefox CI tests');
test.skip(!!process.env.CI && browserName === 'firefox', 'Excluded from Firefox CI tests');


const PASTE_TEXT_FIRST = '*bold*';
const PASTE_TEXT_SECOND = '@here';

const inputLocator = await setupInput(page, 'clear');

await page.evaluate(async (pasteText) => navigator.clipboard.writeText(pasteText), PASTE_TEXT_FIRST);

await inputLocator.press(`${OPERATION_MODIFIER}+v`);
await page.waitForTimeout(TEST_CONST.INPUT_HISTORY_DEBOUNCE_TIME_MS);
await page.evaluate(async (pasteText) => navigator.clipboard.writeText(pasteText), PASTE_TEXT_SECOND);
await inputLocator.press(`${OPERATION_MODIFIER}+v`);
await page.waitForTimeout(TEST_CONST.INPUT_HISTORY_DEBOUNCE_TIME_MS);

await inputLocator.press(`${OPERATION_MODIFIER}+z`);

expect(await inputLocator.innerText()).toBe(PASTE_TEXT_FIRST);
});

test('paste redo', async ({page}) => {
const PASTE_TEXT_FIRST = '*bold*';
const PASTE_TEXT_SECOND = '@here';

const inputLocator = await setupInput(page, 'clear');

await page.evaluate(async (pasteText) => navigator.clipboard.writeText(pasteText), PASTE_TEXT_FIRST);
await inputLocator.press(`${OPERATION_MODIFIER}+v`);
await page.waitForTimeout(TEST_CONST.INPUT_HISTORY_DEBOUNCE_TIME_MS);
await page.evaluate(async (pasteText) => navigator.clipboard.writeText(pasteText), PASTE_TEXT_SECOND);
await page.waitForTimeout(TEST_CONST.INPUT_HISTORY_DEBOUNCE_TIME_MS);
await inputLocator.press(`${OPERATION_MODIFIER}+v`);
await page.waitForTimeout(TEST_CONST.INPUT_HISTORY_DEBOUNCE_TIME_MS);

await inputLocator.press(`${OPERATION_MODIFIER}+z`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using ${OPERATION_MODIFIER}+x everywhere, can we move it to an utility function await pressCmd('x')?

await inputLocator.press(`${OPERATION_MODIFIER}+Shift+z`);

expect(await inputLocator.innerText()).toBe(`${PASTE_TEXT_FIRST}${PASTE_TEXT_SECOND}`);
});
});

test('select', async ({page}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test('select', async ({page}) => {
test('select all', async ({page}) => {

const inputLocator = await setupInput(page, 'reset');
await inputLocator.focus();
await inputLocator.press(`${OPERATION_MODIFIER}+a`);

const cursorPosition = await page.evaluate(checkCursorPosition);

expect(cursorPosition).toBe(TEST_CONST.EXAMPLE_CONTENT.length);
});

test('cut content changes', async ({page, browserName}) => {
test.skip(!!process.env.CI && browserName === 'webkit', 'Excluded from webkit CI tests');

const INITIAL_CONTENT = 'bold';
const WRAPPED_CONTENT = TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.bold.wrapContent(INITIAL_CONTENT);
const EXPECTED_CONTENT = TEST_CONST.MARKDOWN_STYLE_DEFINITIONS.bold.wrapContent(INITIAL_CONTENT).slice(0, 3);

const inputLocator = await setupInput(page, 'clear');
await pasteContent({text: WRAPPED_CONTENT, page, inputLocator});
const rootHandle = await inputLocator.locator('span.root').first();

await page.evaluate(async (initialContent) => {
const filteredNode = Array.from(document.querySelectorAll('div[contenteditable="true"] > span.root span')).find((node) => {
return node.textContent?.includes(initialContent) && node.nextElementSibling && node.nextElementSibling.textContent?.includes('*');
});

const startNode = filteredNode;
const endNode = filteredNode?.nextElementSibling;

if (startNode?.firstChild && endNode?.lastChild) {
const range = new Range();
range.setStart(startNode.firstChild, 2);
range.setEnd(endNode.lastChild, endNode.lastChild.textContent?.length ?? 0);

const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting cursor position can also be moved to utils

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably won't be able to use it in here, because this would be only a part of the function used inside of page.evaluate and we aren't able to pass functions to the interior, as a param. I'll add this to the utils anyway, in case you're ever in need of using it somewhere else.

}, INITIAL_CONTENT);

await inputLocator.focus();
await inputLocator.press(`${OPERATION_MODIFIER}+x`);

expect(await rootHandle.innerHTML()).toBe(EXPECTED_CONTENT);
});
35 changes: 35 additions & 0 deletions WebExample/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type {Page} from '@playwright/test';
import * as TEST_CONST from '../../testConstants';

const setupInput = async (page: Page, action?: 'clear' | 'reset') => {
const inputLocator = await page.locator(`div#${TEST_CONST.INPUT_ID}`);
if (action) await page.click(`[data-testid="${action}"]`);

return inputLocator;
};

const checkCursorPosition = () => {
const editableDiv = document.querySelector('div[contenteditable="true"]') as HTMLElement;
const range = window.getSelection()?.getRangeAt(0);
if (!range || !editableDiv) return null;
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(editableDiv);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length;
};

const setCursorPosition = ({startNode, endNode}: {startNode?: Element; endNode?: Element | null}) => {
if (!startNode?.firstChild || !endNode?.lastChild) return null;

const range = new Range();
range.setStart(startNode.firstChild, 2);
range.setEnd(endNode.lastChild, endNode.lastChild.textContent?.length ?? 0);

const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);

return selection;
};

export {setupInput, checkCursorPosition, setCursorPosition};
2 changes: 1 addition & 1 deletion WebExample/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"compilerOptions": {
"strict": true
},
"include": ["App.tsx"],
"include": ["App.tsx", "**/*.ts", "../testConstants.ts"],
"exclude": ["node_modules"]
}
Loading
Loading