From 675a851e01a070cc2641e18dc5d021c0ac3ef683 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Fri, 9 Feb 2024 20:11:56 +0000 Subject: [PATCH 01/23] feat(playwright): init library with some examples --- packages/playground/.gitignore | 4 + packages/playground/package.json | 6 +- packages/playground/playwright.config.ts | 77 +++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ packages/playground/tests/example.spec.ts | 18 + yarn.lock | 65 +++ 6 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 packages/playground/playwright.config.ts create mode 100644 packages/playground/tests-examples/demo-todo-app.spec.ts create mode 100644 packages/playground/tests/example.spec.ts diff --git a/packages/playground/.gitignore b/packages/playground/.gitignore index a547bf36..b88c8135 100644 --- a/packages/playground/.gitignore +++ b/packages/playground/.gitignore @@ -22,3 +22,7 @@ dist-ssr *.njsproj *.sln *.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/packages/playground/package.json b/packages/playground/package.json index d154b9af..3406a40d 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -11,7 +11,9 @@ "dev:dependencies": "yarn workspaces foreach -Rp --from $npm_package_name --exclude $npm_package_name run dev", "lint": "eslint src --ext .ts,.vue", "lint:ci": "yarn lint --max-warnings 0", - "lint:fix": "yarn lint --fix" + "lint:fix": "yarn lint --fix", + "test": "playwright test", + "test:ui": "yarn test --ui" }, "dependencies": { "@editorjs/dom-adapters": "workspace:^", @@ -19,7 +21,9 @@ "vue": "^3.3.4" }, "devDependencies": { + "@playwright/test": "^1.41.2", "@types/eslint": "^8", + "@types/node": "^20.11.17", "@vitejs/plugin-vue": "^4.2.3", "concurrently": "^8.2.2", "eslint": "^8.53.0", diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts new file mode 100644 index 00000000..301801ee --- /dev/null +++ b/packages/playground/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/packages/playground/tests-examples/demo-todo-app.spec.ts b/packages/playground/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 00000000..2fd6016f --- /dev/null +++ b/packages/playground/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts new file mode 100644 index 00000000..54a906a4 --- /dev/null +++ b/packages/playground/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/yarn.lock b/yarn.lock index 5ae76190..2a5a59f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -575,7 +575,9 @@ __metadata: dependencies: "@editorjs/dom-adapters": "workspace:^" "@editorjs/model": "workspace:^" + "@playwright/test": "npm:^1.41.2" "@types/eslint": "npm:^8" + "@types/node": "npm:^20.11.17" "@vitejs/plugin-vue": "npm:^4.2.3" concurrently: "npm:^8.2.2" eslint: "npm:^8.53.0" @@ -1250,6 +1252,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.41.2": + version: 1.41.2 + resolution: "@playwright/test@npm:1.41.2" + dependencies: + playwright: "npm:1.41.2" + bin: + playwright: cli.js + checksum: e87405987fa024f75acc223c47fcb2da0a66b2fa0cd9a583ca5b02aac12be353d0c262bf6a22b9bc40550c86c8b7629e70cd27f508ec370d9c92bb72f74581e7 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -1530,6 +1543,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.11.17": + version: 20.11.17 + resolution: "@types/node@npm:20.11.17" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 3342df87258d1c56154bcd4b85180f48675427b235971e6e6e2e037353f5a2ae9aaa05ba5df0fe1e2d2f1022c8d856fd39056b9d7f50ea30c0ca3214137cae1d + languageName: node + linkType: hard + "@types/semver@npm:^7.5.0": version: 7.5.5 resolution: "@types/semver@npm:7.5.5" @@ -3650,6 +3672,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 6b5b6f5692372446ff81cf9501c76e3e0459a4852b3b5f1fc72c103198c125a6b8c72f5f166bdd76ffb2fca261e7f6ee5565daf80dca6e571e55bcc589cc1256 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -3660,6 +3692,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -5837,6 +5878,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.41.2": + version: 1.41.2 + resolution: "playwright-core@npm:1.41.2" + bin: + playwright-core: cli.js + checksum: 77ff881ebb9cc0654edd00c5ff202f5f61aee7a5318e1f12a82e30a3636de21e8b5982fae6138e5bb90115ae509c15a640cf85b10b3e2daebb2bb286da54fd4c + languageName: node + linkType: hard + +"playwright@npm:1.41.2": + version: 1.41.2 + resolution: "playwright@npm:1.41.2" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.41.2" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 272399f622dc2df90fbef147b9b1cfab5d7a78cc364bdfa98d2bf08faa9894346f58629fe4fef41b108ca2cb203b3970d7886b7f392cb0399c75b521478e2920 + languageName: node + linkType: hard + "postcss-selector-parser@npm:^6.0.13": version: 6.0.13 resolution: "postcss-selector-parser@npm:6.0.13" From eddf10a8aa958d10b120e16063b4748ba2638906 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:58:46 +0000 Subject: [PATCH 02/23] feat(playwright): configure web server, adapt example file suite for playground --- packages/playground/.eslintrc.cjs | 1 + packages/playground/package.json | 2 +- packages/playground/playwright.config.ts | 12 ++++----- packages/playground/src/components/Input.vue | 1 + packages/playground/tests/example.spec.ts | 27 ++++++++++++-------- packages/playground/tsconfig.node.json | 3 ++- packages/playground/tsconfig.playwright.json | 8 ++++++ 7 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 packages/playground/tsconfig.playwright.json diff --git a/packages/playground/.eslintrc.cjs b/packages/playground/.eslintrc.cjs index 7c4ace9f..c6541b81 100644 --- a/packages/playground/.eslintrc.cjs +++ b/packages/playground/.eslintrc.cjs @@ -10,6 +10,7 @@ module.exports = { project: [ './tsconfig.json', './tsconfig.eslint.json', + './tsconfig.playwright.json' ], ecmaVersion: 2022, extraFileExtensions: [ '.vue' ], diff --git a/packages/playground/package.json b/packages/playground/package.json index 3406a40d..e8d04015 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -4,7 +4,7 @@ "type": "module", "packageManager": "yarn@4.0.1", "scripts": { - "dev": "concurrently -n \"TSC Watch\",Vite \"yarn dev:dependencies\" \"vite\"", + "dev": "concurrently -n \"TSC Watch\",Vite \"yarn dev:dependencies\" \"vite --port 3123\"", "build": "yarn build:dependencies && vue-tsc && vite build", "preview": "yarn build:dependencies && vite preview", "build:dependencies": "yarn workspaces foreach -Rpt --from $npm_package_name --exclude $npm_package_name run build", diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index 301801ee..fb6168f6 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -24,7 +24,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: 'http://localhost:3123', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -69,9 +69,9 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + webServer: { + command: 'yarn dev', + url: 'http://localhost:3123', + reuseExistingServer: !process.env.CI, + }, }); diff --git a/packages/playground/src/components/Input.vue b/packages/playground/src/components/Input.vue index 00bf5d83..d1a47e41 100644 --- a/packages/playground/src/components/Input.vue +++ b/packages/playground/src/components/Input.vue @@ -37,6 +37,7 @@ onMounted(() => {
{ - await page.goto('https://playwright.dev/'); +test.beforeEach(async ({ page }) => { + await page.goto('/'); +}); - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); +test('has title', async ({ page }) => { + await expect(page).toHaveTitle(/Playground/); }); -test('get started link', async ({ page }) => { - await page.goto('https://playwright.dev/'); +test.describe('input field', () => { + test('should be visible', async ({ page }) => { + const input = page.getByRole('textbox'); + + await expect(input).toBeVisible(); + }); + + test('should be focusable', async ({ page }) => { + const input = page.getByRole('textbox'); - // Click the get started link. - await page.getByRole('link', { name: 'Get started' }).click(); + await input.click(); - // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); + await expect(input).toBeFocused(); + }); }); diff --git a/packages/playground/tsconfig.node.json b/packages/playground/tsconfig.node.json index b5a34318..4b497b39 100644 --- a/packages/playground/tsconfig.node.json +++ b/packages/playground/tsconfig.node.json @@ -7,6 +7,7 @@ "allowSyntheticDefaultImports": true }, "include": [ - "vite.config.ts" + "vite.config.ts", + "playwright.config.ts" ] } diff --git a/packages/playground/tsconfig.playwright.json b/packages/playground/tsconfig.playwright.json new file mode 100644 index 00000000..db25ade2 --- /dev/null +++ b/packages/playground/tsconfig.playwright.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "strict": true, + }, + "include": [ + "tests/**/*.ts" + ] +} From 08799e502a992b582bb3c5f4b44bd0814ec0306d Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:59:50 +0000 Subject: [PATCH 03/23] chore(playwright): remove examples --- .../tests-examples/demo-todo-app.spec.ts | 437 ------------------ 1 file changed, 437 deletions(-) delete mode 100644 packages/playground/tests-examples/demo-todo-app.spec.ts diff --git a/packages/playground/tests-examples/demo-todo-app.spec.ts b/packages/playground/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index 2fd6016f..00000000 --- a/packages/playground/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -]; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); - - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); - - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - }); - - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); - - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); - }); - - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); - - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); - }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); - - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); - }); - - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); -}); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} From 12e8fe38119a53eebbce4d27de6319af47495b01 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Tue, 13 Feb 2024 19:07:54 +0000 Subject: [PATCH 04/23] test(input): add test case for typing --- packages/playground/tests/example.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index 450dd322..b57f8639 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -22,4 +22,12 @@ test.describe('input field', () => { await expect(input).toBeFocused(); }); + + test('should accept text', async ({ page }) => { + const input = page.getByRole('textbox'); + + await input.pressSequentially('Hello, World!', { delay: 100 }); + + await expect(input).toHaveText('Hello, World!'); + }); }); From 007fb761e3b609e475f9dd2ec6431ccad02b231d Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:19:12 +0000 Subject: [PATCH 05/23] ci(playwright): add action for running e2e tests --- .github/actions/e2e-tests/action.yml | 36 ++++++++++++++++++++++++ packages/playground/playwright.config.ts | 5 +++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .github/actions/e2e-tests/action.yml diff --git a/.github/actions/e2e-tests/action.yml b/.github/actions/e2e-tests/action.yml new file mode 100644 index 00000000..5933b9e1 --- /dev/null +++ b/.github/actions/e2e-tests/action.yml @@ -0,0 +1,36 @@ +name: E2E tests +description: This action runs end-to-end tests using Playwright +inputs: + working-directory: + description: 'Path to the ./packages/name_of_your_package_folder' + required: true + package-name: + description: 'A full name of the package' + required: true + +runs: + using: "composite" + steps: + - uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + - uses: ./.github/actions/setup + + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + working-directory: ${{ inputs.working-directory }} + shell: bash + + - name: Run Playwright tests + run: yarn playwright test + working-directory: ${{ inputs.working-directory }} + shell: bash + + - name: Upload Playwright report to GitHub artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{ inputs.package-name }}-playwright-report + path: ${{ inputs.working-directory }}/playwright-report/ + retention-days: 7 + diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index fb6168f6..842bfbbe 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -20,7 +20,10 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [ + ['html'], + [process.env.CI ? 'github' : 'line'] + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ From 7f1ca88b08fafd1b0e22ce5dcc22a044b36415f1 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:20:15 +0000 Subject: [PATCH 06/23] ci(playground): add job for running e2e tests --- .github/workflows/playground.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index 4811f740..ff54b7bf 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -21,3 +21,13 @@ jobs: uses: ./.github/actions/build with: package-name: '@editorjs/document-playground' + + e2e-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run E2E tests + uses: ./.github/actions/e2e-tests + with: + working-directory: packages/playground + package-name: '@editorjs/document-playground' From eff384bc0b924218f11c73d2e796591ce26e2493 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:28:25 +0000 Subject: [PATCH 07/23] fix(e2e-tests): pass artifact report name to the action --- .github/actions/e2e-tests/action.yml | 6 +++--- .github/workflows/playground.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/e2e-tests/action.yml b/.github/actions/e2e-tests/action.yml index 5933b9e1..db848f0f 100644 --- a/.github/actions/e2e-tests/action.yml +++ b/.github/actions/e2e-tests/action.yml @@ -4,8 +4,8 @@ inputs: working-directory: description: 'Path to the ./packages/name_of_your_package_folder' required: true - package-name: - description: 'A full name of the package' + artifactory-report-name: + description: 'A full name of the artifact report' required: true runs: @@ -30,7 +30,7 @@ runs: uses: actions/upload-artifact@v3 if: always() with: - name: ${{ inputs.package-name }}-playwright-report + name: ${{ inputs.artifactory-report-name }} path: ${{ inputs.working-directory }}/playwright-report/ retention-days: 7 diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index ff54b7bf..80d0c606 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -30,4 +30,4 @@ jobs: uses: ./.github/actions/e2e-tests with: working-directory: packages/playground - package-name: '@editorjs/document-playground' + artifactory-report-name: 'document-playground-e2e-report' From 60c3845fa73636bdfd6d8c42805a3d1863e87750 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:32:05 +0000 Subject: [PATCH 08/23] fix(playwright): enable reuseExistingServer --- packages/playground/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index 842bfbbe..a73dc50e 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -75,6 +75,6 @@ export default defineConfig({ webServer: { command: 'yarn dev', url: 'http://localhost:3123', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, }, }); From 976c01c9ed0ee771062ace469cea1ed91ba5958d Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:34:18 +0000 Subject: [PATCH 09/23] revert: fix(playwright): enable reuseExistingServer --- packages/playground/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index a73dc50e..842bfbbe 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -75,6 +75,6 @@ export default defineConfig({ webServer: { command: 'yarn dev', url: 'http://localhost:3123', - reuseExistingServer: true, + reuseExistingServer: !process.env.CI, }, }); From 8fbf0bfa48473327ea09e633458c891f98528f82 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:43:34 +0000 Subject: [PATCH 10/23] fix(page-loading): add waitForLoadState to prevent flaky tests --- packages/playground/tests/example.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index b57f8639..a33f5529 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -2,6 +2,7 @@ import { test, expect } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.goto('/'); + await page.waitForLoadState(undefined, { timeout: 5000 }); }); test('has title', async ({ page }) => { From 05ac89fe2550dda46009dae230f80a8e5f8e2e7d Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:48:57 +0000 Subject: [PATCH 11/23] fix(page-testing): wait for domcontentloaded event --- packages/playground/tests/example.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index a33f5529..70ba2a4b 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.goto('/'); - await page.waitForLoadState(undefined, { timeout: 5000 }); + await page.waitForLoadState('domcontentloaded', { timeout: 5000 }); }); test('has title', async ({ page }) => { From af49145ffe789a15be87bbbd4f41146a16909c99 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:52:07 +0000 Subject: [PATCH 12/23] ci(playwright): remove workers set up --- packages/playground/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index 842bfbbe..f078730b 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + // workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ['html'], From ba41961ce20b590b48a4c7ce9b04ddc11dc61554 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:56:23 +0000 Subject: [PATCH 13/23] fix(page-testing): wait for networkidle event --- packages/playground/tests/example.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index 70ba2a4b..acfeff8d 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.goto('/'); - await page.waitForLoadState('domcontentloaded', { timeout: 5000 }); + await page.waitForLoadState('networkidle'); }); test('has title', async ({ page }) => { From 9450e827d0934cfc8ddbc2cf601a56beb834e340 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:09:00 +0000 Subject: [PATCH 14/23] refactor(e2e-tests): create separate command for e2e dev server --- .github/actions/e2e-tests/action.yml | 5 +++++ packages/playground/package.json | 3 ++- packages/playground/playwright.config.ts | 3 ++- packages/playground/tests/example.spec.ts | 1 - 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/actions/e2e-tests/action.yml b/.github/actions/e2e-tests/action.yml index db848f0f..3ef33072 100644 --- a/.github/actions/e2e-tests/action.yml +++ b/.github/actions/e2e-tests/action.yml @@ -21,6 +21,11 @@ runs: working-directory: ${{ inputs.working-directory }} shell: bash + - name: Build the package + run: yarn build + working-directory: ${{ inputs.working-directory }} + shell: bash + - name: Run Playwright tests run: yarn playwright test working-directory: ${{ inputs.working-directory }} diff --git a/packages/playground/package.json b/packages/playground/package.json index e8d04015..4240cd4a 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -13,7 +13,8 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "playwright test", - "test:ui": "yarn test --ui" + "test:ui": "yarn test --ui", + "e2e-dev-server": "vite --port 3123" }, "dependencies": { "@editorjs/dom-adapters": "workspace:^", diff --git a/packages/playground/playwright.config.ts b/packages/playground/playwright.config.ts index f078730b..9cda4fee 100644 --- a/packages/playground/playwright.config.ts +++ b/packages/playground/playwright.config.ts @@ -73,8 +73,9 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: 'yarn dev', + command: 'yarn e2e-dev-server', url: 'http://localhost:3123', reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, }, }); diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index acfeff8d..b57f8639 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -2,7 +2,6 @@ import { test, expect } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.goto('/'); - await page.waitForLoadState('networkidle'); }); test('has title', async ({ page }) => { From 8a673427d3a50d9b43d52e5524f821baa981adbe Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:12:17 +0000 Subject: [PATCH 15/23] feat(e2e-dev-server): add build deps command --- packages/playground/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/package.json b/packages/playground/package.json index 4240cd4a..b5e8b21c 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -14,7 +14,7 @@ "lint:fix": "yarn lint --fix", "test": "playwright test", "test:ui": "yarn test --ui", - "e2e-dev-server": "vite --port 3123" + "e2e-dev-server": "yarn build:dependencies && vite --port 3123" }, "dependencies": { "@editorjs/dom-adapters": "workspace:^", From cf1f54cfac1a280c5adb40914a41d24027a51a42 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:15:01 +0000 Subject: [PATCH 16/23] ci(e2e-tests): remove build step --- .github/actions/e2e-tests/action.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/actions/e2e-tests/action.yml b/.github/actions/e2e-tests/action.yml index 3ef33072..db848f0f 100644 --- a/.github/actions/e2e-tests/action.yml +++ b/.github/actions/e2e-tests/action.yml @@ -21,11 +21,6 @@ runs: working-directory: ${{ inputs.working-directory }} shell: bash - - name: Build the package - run: yarn build - working-directory: ${{ inputs.working-directory }} - shell: bash - - name: Run Playwright tests run: yarn playwright test working-directory: ${{ inputs.working-directory }} From 3484f36e10849e3ab8d28e4c33859fc0ce697498 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Wed, 28 Aug 2024 23:12:28 +0100 Subject: [PATCH 17/23] fix(tests): correct assertion for input element --- packages/playground/tests/example.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts index b57f8639..33445b49 100644 --- a/packages/playground/tests/example.spec.ts +++ b/packages/playground/tests/example.spec.ts @@ -28,6 +28,6 @@ test.describe('input field', () => { await input.pressSequentially('Hello, World!', { delay: 100 }); - await expect(input).toHaveText('Hello, World!'); + await expect(input).toHaveValue('Hello, World!'); }); }); From 1960837db68ff84512776c0450f25275a01465ef Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:12:39 +0100 Subject: [PATCH 18/23] feat(playwright): create fixture for playground page --- .../tests/fixtures/PlaygroundPage.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/playground/tests/fixtures/PlaygroundPage.ts diff --git a/packages/playground/tests/fixtures/PlaygroundPage.ts b/packages/playground/tests/fixtures/PlaygroundPage.ts new file mode 100644 index 00000000..f579a78e --- /dev/null +++ b/packages/playground/tests/fixtures/PlaygroundPage.ts @@ -0,0 +1,49 @@ +import type { Locator, Page } from '@playwright/test'; + +/** + * Playground page fixture to help access the page elements + */ +export class PlaygroundPage { + private readonly inputLocator: Locator; + private readonly textareaLocator: Locator; + private readonly contenteditableLocator: Locator; + + /** + * Sets locators for input, textarea and contenteditable elements + * + * @param page - Playwright page object + */ + constructor(public readonly page: Page) { + this.inputLocator = page.locator('input'); + this.textareaLocator = page.locator('textarea'); + this.contenteditableLocator = page.locator('[contenteditable]').first(); // hack to get the first contenteditable element + } + + /** + * Navigates to the playground page + */ + public async goto(): Promise { + await this.page.goto('/'); + } + + /** + * Returns the native input locator + */ + public get input(): Locator { + return this.inputLocator; + } + + /** + * Returns the textarea locator + */ + public get textarea(): Locator { + return this.textareaLocator; + } + + /** + * Returns the contenteditable locator + */ + public get contenteditable(): Locator { + return this.contenteditableLocator; + } +} From 8834deaa83de6c3961f6bb760864f3a74b38d8f2 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:13:09 +0100 Subject: [PATCH 19/23] feat(playwright): extend default test function with playground page fixture --- packages/playground/tests/utils/test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/playground/tests/utils/test.ts diff --git a/packages/playground/tests/utils/test.ts b/packages/playground/tests/utils/test.ts new file mode 100644 index 00000000..9f53eb06 --- /dev/null +++ b/packages/playground/tests/utils/test.ts @@ -0,0 +1,15 @@ +import { test as base } from '@playwright/test'; +import { PlaygroundPage } from '../fixtures/PlaygroundPage'; + +/** + * + */ +export const test = base.extend<{ playgroundPage: PlaygroundPage }>({ + playgroundPage: async ({ page }, use) => { + const playgroundPage = new PlaygroundPage(page); + + await playgroundPage.goto(); + + await use(playgroundPage); + }, +}); From b6772e20742a26f8388cb47f22a6dce7310f0a0a Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:13:26 +0100 Subject: [PATCH 20/23] feat(playwright): add basic tests for all input elements --- .../playground/tests/contenteditable.spec.ts | 30 +++++++++++++++++ packages/playground/tests/example.spec.ts | 33 ------------------- packages/playground/tests/input.spec.ts | 30 +++++++++++++++++ packages/playground/tests/textarea.spec.ts | 30 +++++++++++++++++ 4 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 packages/playground/tests/contenteditable.spec.ts delete mode 100644 packages/playground/tests/example.spec.ts create mode 100644 packages/playground/tests/input.spec.ts create mode 100644 packages/playground/tests/textarea.spec.ts diff --git a/packages/playground/tests/contenteditable.spec.ts b/packages/playground/tests/contenteditable.spec.ts new file mode 100644 index 00000000..8310665f --- /dev/null +++ b/packages/playground/tests/contenteditable.spec.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import { test } from './utils/test'; + +test.describe('Contenteditable field', () => { + test('should be visible', async ({ playgroundPage }) => { + const contenteditable = playgroundPage.contenteditable; + + await expect(contenteditable).toBeVisible(); + }); + + test('should be focusable', async ({ playgroundPage }) => { + const contenteditable = playgroundPage.contenteditable; + + await contenteditable.click(); + + await expect(contenteditable).toBeFocused(); + }); + + test('should accept text', async ({ playgroundPage }) => { + const contenteditable = playgroundPage.contenteditable; + const initialText = await contenteditable.textContent(); + const inputText = 'Hello, World!'; + const expectedText = initialText + inputText; + + await contenteditable.click(); + await contenteditable.pressSequentially(inputText); + + await expect(contenteditable).toHaveText(expectedText); + }); +}); diff --git a/packages/playground/tests/example.spec.ts b/packages/playground/tests/example.spec.ts deleted file mode 100644 index 33445b49..00000000 --- a/packages/playground/tests/example.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('/'); -}); - -test('has title', async ({ page }) => { - await expect(page).toHaveTitle(/Playground/); -}); - -test.describe('input field', () => { - test('should be visible', async ({ page }) => { - const input = page.getByRole('textbox'); - - await expect(input).toBeVisible(); - }); - - test('should be focusable', async ({ page }) => { - const input = page.getByRole('textbox'); - - await input.click(); - - await expect(input).toBeFocused(); - }); - - test('should accept text', async ({ page }) => { - const input = page.getByRole('textbox'); - - await input.pressSequentially('Hello, World!', { delay: 100 }); - - await expect(input).toHaveValue('Hello, World!'); - }); -}); diff --git a/packages/playground/tests/input.spec.ts b/packages/playground/tests/input.spec.ts new file mode 100644 index 00000000..9a0dabfa --- /dev/null +++ b/packages/playground/tests/input.spec.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import { test } from './utils/test'; + +test.describe('Native input element', () => { + test('should be visible', async ({ playgroundPage }) => { + const input = playgroundPage.input; + + await expect(input).toBeVisible(); + }); + + test('should be focusable', async ({ playgroundPage }) => { + const input = playgroundPage.input; + + await input.click(); + + await expect(input).toBeFocused(); + }); + + test('should accept text', async ({ playgroundPage }) => { + const input = playgroundPage.input; + const initialText = await input.inputValue(); + const inputText = 'Hello, World!'; + const expectedText = initialText + inputText; + + await input.click(); + await input.pressSequentially(inputText, { delay: 100 }); + + await expect(input).toHaveValue(expectedText); + }); +}); diff --git a/packages/playground/tests/textarea.spec.ts b/packages/playground/tests/textarea.spec.ts new file mode 100644 index 00000000..3672506b --- /dev/null +++ b/packages/playground/tests/textarea.spec.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import { test } from './utils/test'; + +test.describe('Textarea field', () => { + test('should be visible', async ({ playgroundPage }) => { + const textarea = playgroundPage.textarea; + + await expect(textarea).toBeVisible(); + }); + + test('should be focusable', async ({ playgroundPage }) => { + const textarea = playgroundPage.textarea; + + await textarea.click(); + + await expect(textarea).toBeFocused(); + }); + + test('should accept text', async ({ playgroundPage }) => { + const textarea = playgroundPage.textarea; + const initialText = await textarea.inputValue(); + const inputText = 'Hello, World!'; + const expectedText = initialText + inputText; + + await textarea.click(); + await textarea.pressSequentially(inputText, { delay: 100 }); + + await expect(textarea).toHaveValue(expectedText); + }); +}); From d28dac53e055e5917402ccde1edbaac95f8c9244 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:16:59 +0100 Subject: [PATCH 21/23] feat(playwright): make fields private --- .../tests/fixtures/PlaygroundPage.ts | 18 +++++++++--------- packages/playground/tsconfig.playwright.json | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/playground/tests/fixtures/PlaygroundPage.ts b/packages/playground/tests/fixtures/PlaygroundPage.ts index f579a78e..44e95378 100644 --- a/packages/playground/tests/fixtures/PlaygroundPage.ts +++ b/packages/playground/tests/fixtures/PlaygroundPage.ts @@ -4,9 +4,9 @@ import type { Locator, Page } from '@playwright/test'; * Playground page fixture to help access the page elements */ export class PlaygroundPage { - private readonly inputLocator: Locator; - private readonly textareaLocator: Locator; - private readonly contenteditableLocator: Locator; + readonly #input: Locator; + readonly #textarea: Locator; + readonly #contenteditable: Locator; /** * Sets locators for input, textarea and contenteditable elements @@ -14,9 +14,9 @@ export class PlaygroundPage { * @param page - Playwright page object */ constructor(public readonly page: Page) { - this.inputLocator = page.locator('input'); - this.textareaLocator = page.locator('textarea'); - this.contenteditableLocator = page.locator('[contenteditable]').first(); // hack to get the first contenteditable element + this.#input = page.locator('input'); + this.#textarea = page.locator('textarea'); + this.#contenteditable = page.locator('[contenteditable]').first(); // hack to get the first contenteditable element } /** @@ -30,20 +30,20 @@ export class PlaygroundPage { * Returns the native input locator */ public get input(): Locator { - return this.inputLocator; + return this.#input; } /** * Returns the textarea locator */ public get textarea(): Locator { - return this.textareaLocator; + return this.#textarea; } /** * Returns the contenteditable locator */ public get contenteditable(): Locator { - return this.contenteditableLocator; + return this.#contenteditable; } } diff --git a/packages/playground/tsconfig.playwright.json b/packages/playground/tsconfig.playwright.json index db25ade2..69fd1ad1 100644 --- a/packages/playground/tsconfig.playwright.json +++ b/packages/playground/tsconfig.playwright.json @@ -1,4 +1,5 @@ { + "extends": "./tsconfig.json", "compilerOptions": { "strict": true, }, From f513bc9b6718c8204ff8fa324bbc8d3eb2d359e4 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:32:08 +0100 Subject: [PATCH 22/23] feat(e2e-tests): move caret to the end of input elements --- packages/playground/tests/contenteditable.spec.ts | 1 + packages/playground/tests/input.spec.ts | 1 + packages/playground/tests/textarea.spec.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/playground/tests/contenteditable.spec.ts b/packages/playground/tests/contenteditable.spec.ts index 8310665f..3ba7a2ae 100644 --- a/packages/playground/tests/contenteditable.spec.ts +++ b/packages/playground/tests/contenteditable.spec.ts @@ -23,6 +23,7 @@ test.describe('Contenteditable field', () => { const expectedText = initialText + inputText; await contenteditable.click(); + await contenteditable.press('Control+ArrowRight'); await contenteditable.pressSequentially(inputText); await expect(contenteditable).toHaveText(expectedText); diff --git a/packages/playground/tests/input.spec.ts b/packages/playground/tests/input.spec.ts index 9a0dabfa..13e6a588 100644 --- a/packages/playground/tests/input.spec.ts +++ b/packages/playground/tests/input.spec.ts @@ -23,6 +23,7 @@ test.describe('Native input element', () => { const expectedText = initialText + inputText; await input.click(); + await input.press('Control+ArrowRight'); await input.pressSequentially(inputText, { delay: 100 }); await expect(input).toHaveValue(expectedText); diff --git a/packages/playground/tests/textarea.spec.ts b/packages/playground/tests/textarea.spec.ts index 3672506b..3231eb19 100644 --- a/packages/playground/tests/textarea.spec.ts +++ b/packages/playground/tests/textarea.spec.ts @@ -23,6 +23,7 @@ test.describe('Textarea field', () => { const expectedText = initialText + inputText; await textarea.click(); + await textarea.press('Control+ArrowRight'); await textarea.pressSequentially(inputText, { delay: 100 }); await expect(textarea).toHaveValue(expectedText); From c99717f515b85f866b4bfe3cc355abd7a9a60736 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:39:23 +0100 Subject: [PATCH 23/23] docs(playwright): add comment to extended test function --- packages/playground/tests/utils/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/tests/utils/test.ts b/packages/playground/tests/utils/test.ts index 9f53eb06..fe4f6dde 100644 --- a/packages/playground/tests/utils/test.ts +++ b/packages/playground/tests/utils/test.ts @@ -2,7 +2,7 @@ import { test as base } from '@playwright/test'; import { PlaygroundPage } from '../fixtures/PlaygroundPage'; /** - * + * Custom test wrapper to extend the base test with the playground page fixture */ export const test = base.extend<{ playgroundPage: PlaygroundPage }>({ playgroundPage: async ({ page }, use) => {