From ca4e5db8b1bfa5daa70da09e0c79d63e5578286b Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Wed, 8 May 2024 20:21:56 +0200 Subject: [PATCH] refactor(e2e): create editor helpers (#54701) --- e2e/code-storage.spec.ts | 5 +++- e2e/editor.spec.ts | 33 +++---------------------- e2e/hotkeys.spec.ts | 11 ++++----- e2e/multifile-editor.spec.ts | 4 ++- e2e/output.spec.ts | 4 ++- e2e/preview.spec.ts | 11 ++------- e2e/progress-bar.spec.ts | 39 +++++++++++++---------------- e2e/reset-modal.spec.ts | 15 +++++------ e2e/shortcuts-modal.spec.ts | 5 ++-- e2e/utils/editor.ts | 48 ++++++++++++++++++++++++++++++++++++ 10 files changed, 93 insertions(+), 82 deletions(-) create mode 100644 e2e/utils/editor.ts diff --git a/e2e/code-storage.spec.ts b/e2e/code-storage.spec.ts index 3b5593790ad..64ef7a8be98 100644 --- a/e2e/code-storage.spec.ts +++ b/e2e/code-storage.spec.ts @@ -1,4 +1,7 @@ import { test, expect } from '@playwright/test'; + +import { getEditors } from './utils/editor'; + test.use({ storageState: 'playwright/.auth/certified-user.json' }); test.describe('Challenge with editor', function () { test('the shortcut "Ctrl + S" saves the code', async ({ page }) => { @@ -6,7 +9,7 @@ test.describe('Challenge with editor', function () { '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-2' ); - const editor = page.locator('textarea'); + const editor = getEditors(page); await editor.fill('Something funny'); await page.keyboard.down('Control'); diff --git a/e2e/editor.spec.ts b/e2e/editor.spec.ts index b6fadf69c5e..a2e75e64de3 100644 --- a/e2e/editor.spec.ts +++ b/e2e/editor.spec.ts @@ -1,26 +1,6 @@ -import { expect, test, type Page } from '@playwright/test'; +import { expect, test } from '@playwright/test'; -async function focusEditor({ - page, - isMobile, - browserName -}: { - page: Page; - isMobile: boolean; - browserName: string; -}) { - const monacoEditor = page.getByLabel('Editor content'); - - // The editor has an overlay div, which prevents the click event from bubbling up in iOS Safari. - // This is a quirk in this browser-OS combination, and the workaround here is to use `.focus()` - // in place of `.click()` to focus on the editor. - // Ref: https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if (isMobile && browserName === 'webkit') { - await monacoEditor.focus(); - } else { - await monacoEditor.click(); - } -} +import { clearEditor, focusEditor } from './utils/editor'; test.describe('Editor Component', () => { test('should allow the user to insert text', async ({ @@ -50,14 +30,7 @@ test.describe('Python Terminal', () => { ); await focusEditor({ page, isMobile, browserName }); - // First clear the editor - // TODO: replace with ControlOrMeta when it's supported - if (browserName === 'webkit') { - await page.keyboard.press('Meta+a'); - } else { - await page.keyboard.press('Control+a'); - } - await page.keyboard.press('Backspace'); + await clearEditor({ page, browserName }); // Then enter invalid code await page.keyboard.insertText('def'); const preview = page.getByTestId('preview-pane'); diff --git a/e2e/hotkeys.spec.ts b/e2e/hotkeys.spec.ts index 9d77b2df381..b17016eefd0 100644 --- a/e2e/hotkeys.spec.ts +++ b/e2e/hotkeys.spec.ts @@ -2,11 +2,10 @@ import { test, expect } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; import { authedPut } from './utils/request'; +import { getEditors } from './utils/editor'; const course = '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code'; -const editorPaneLabel = - 'Editor content;Press Alt+F1 for Accessibility Options.'; test.use({ storageState: 'playwright/.auth/certified-user.json' }); @@ -41,9 +40,9 @@ test('User can interact with the app using the keyboard', async ({ page }) => { await page.goto(course); - await expect(page.getByLabel(editorPaneLabel)).toBeFocused(); - await page.getByLabel(editorPaneLabel).press('Escape'); - await expect(page.getByLabel(editorPaneLabel)).not.toBeFocused(); + await expect(getEditors(page)).toBeFocused(); + await getEditors(page).press('Escape'); + await expect(getEditors(page)).not.toBeFocused(); await page.keyboard.press('n'); const nextCourse = '**/declare-javascript-variables'; @@ -62,7 +61,7 @@ test('User can interact with the app using the keyboard', async ({ page }) => { ).toBeVisible(); await page.keyboard.press('e'); - await expect(page.getByLabel(editorPaneLabel)).toBeFocused(); + await expect(getEditors(page)).toBeFocused(); await page.keyboard.press('Control+Enter'); await expect(page.getByText('running test')).toBeVisible(); diff --git a/e2e/multifile-editor.spec.ts b/e2e/multifile-editor.spec.ts index eca72f55515..8fbeb117baa 100644 --- a/e2e/multifile-editor.spec.ts +++ b/e2e/multifile-editor.spec.ts @@ -1,5 +1,7 @@ import { test, expect } from '@playwright/test'; +import { getEditors } from './utils/editor'; + test.beforeEach(async ({ page }) => { await page.goto( '/learn/2022/responsive-web-design/learn-basic-css-by-building-a-cafe-menu/step-15' @@ -21,7 +23,7 @@ test.describe('MultifileEditor Component', () => { // before moving onto other assertions. // Note that using the `.all()` locator here would result a flaky test. // Ref: https://github.com/freeCodeCamp/freeCodeCamp/pull/53031/files#r1500316812 - const editors = page.getByLabel('Editor content'); + const editors = getEditors(page); await expect(editors).toHaveCount(2); const test_string = 'TestString'; diff --git a/e2e/output.spec.ts b/e2e/output.spec.ts index 38968eb981c..9d43ef29514 100644 --- a/e2e/output.spec.ts +++ b/e2e/output.spec.ts @@ -1,5 +1,7 @@ import { test, expect, type Page } from '@playwright/test'; + import translations from '../client/i18n/locales/english/translations.json'; +import { getEditors } from './utils/editor'; const outputTexts = { default: ` @@ -33,7 +35,7 @@ const insertTextInCodeEditor = async ({ .getByRole('tab', { name: translations.learn['editor-tabs'].code }) .click(); } - await page.getByLabel('Editor content').fill(text); + await getEditors(page).fill(text); if (isMobile) { await page .getByRole('tab', { name: translations.learn['editor-tabs'].console }) diff --git a/e2e/preview.spec.ts b/e2e/preview.spec.ts index 7a55af398b6..30203df1dce 100644 --- a/e2e/preview.spec.ts +++ b/e2e/preview.spec.ts @@ -1,5 +1,6 @@ import { expect, test } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; +import { focusEditor } from './utils/editor'; test.beforeEach(async ({ page }) => { await page.goto( @@ -30,15 +31,7 @@ test.describe('Challenge Preview Component', () => { browserName, isMobile }) => { - // The editor has an overlay div, which prevents the click event from bubbling up in iOS Safari. - // This is a quirk in this browser-OS combination, and the workaround here is to use `.focus()` - // in place of `.click()` to focus on the editor. - // Ref: https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if (isMobile && browserName === 'webkit') { - await page.getByLabel('Editor content').focus(); - } else { - await page.getByLabel('Editor content').click(); - } + await focusEditor({ page, isMobile, browserName }); await page.keyboard.insertText('

FreeCodeCamp

'); if (isMobile) { diff --git a/e2e/progress-bar.spec.ts b/e2e/progress-bar.spec.ts index ab46b7b16e2..cc9ef86aa86 100644 --- a/e2e/progress-bar.spec.ts +++ b/e2e/progress-bar.spec.ts @@ -1,26 +1,25 @@ import { expect, test } from '@playwright/test'; +import { clearEditor, focusEditor } from './utils/editor'; + test.use({ storageState: 'playwright/.auth/certified-user.json' }); test.describe('Progress bar component', () => { test('Should appear with the correct content after the user has submitted their code', async ({ - page + page, + isMobile, + browserName }) => { await page.goto( - '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-2' + '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-3' ); - - const monacoEditor = page.getByLabel('Editor content'); - - // Using focus instead of click since we're not testing if the editor - // behaves correctly, we're using it to complete the challenge. - await monacoEditor.focus(); - await page.keyboard.press('Control+A'); - //Meta + A works in webkit - await page.keyboard.press('Meta+A'); - await page.keyboard.press('Backspace'); + // If focusEditor fails, typically it's because the instructions are too + // large. There's a bug that means `scrollIntoView` does not work in the + // editor and so we have to pick less verbose challenges until that's fixed. + await focusEditor({ page, isMobile, browserName }); + await clearEditor({ page, browserName }); await page.keyboard.insertText( - '

CatPhotoApp

Cat Photos

' + '

CatPhotoApp

Cat Photos

See more cat photos in our gallery.

' ); await page.getByRole('button', { name: 'Check Your Code' }).click(); @@ -36,19 +35,15 @@ test.describe('Progress bar component', () => { }); test('should appear in the completion modal after user has submitted their code', async ({ - page + page, + isMobile, + browserName }) => { await page.goto( '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-javascript-variables' ); - - const monacoEditor = page.getByLabel('Editor content'); - await monacoEditor.focus(); - - await page.keyboard.press('Control+A'); - - await page.keyboard.press('Meta+A'); - await page.keyboard.press('Backspace'); + await focusEditor({ page, isMobile, browserName }); + await clearEditor({ page, browserName }); await page.keyboard.insertText('var myName;'); diff --git a/e2e/reset-modal.spec.ts b/e2e/reset-modal.spec.ts index 838304d2974..7e37f0eca48 100644 --- a/e2e/reset-modal.spec.ts +++ b/e2e/reset-modal.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; +import { clearEditor, focusEditor, getEditors } from './utils/editor'; test('should render the modal content correctly', async ({ page }) => { await page.goto( @@ -36,7 +37,7 @@ test('should render the modal content correctly', async ({ page }) => { ).toBeVisible(); }); -test('User can reset challenge', async ({ page }) => { +test('User can reset challenge', async ({ page, isMobile, browserName }) => { const initialText = 'CatPhotoApp'; const initialFrame = page .frameLocator('iframe[title="challenge preview"]') @@ -54,13 +55,11 @@ test('User can reset challenge', async ({ page }) => { // Building the preview can take a while await expect(initialFrame).toBeVisible({ timeout: 10000 }); - const editorPaneLabel = - 'Editor content;Press Alt+F1 for Accessibility Options.'; - // Modify the text in the editor pane, clearing first, otherwise the existing // text will be selected before typing - await page.getByLabel(editorPaneLabel).fill(''); - await page.getByLabel(editorPaneLabel).fill(updatedText); + await focusEditor({ page, isMobile, browserName }); + await clearEditor({ page, browserName }); + await getEditors(page).fill(updatedText); await expect(updatedFrame).toBeVisible({ timeout: 10000 }); // Run the tests so the lower jaw updates (later we confirm that the update @@ -95,11 +94,9 @@ test('User can reset classic challenge', async ({ page, isMobile }) => { '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code' ); - const editorPaneLabel = - 'Editor content;Press Alt+F1 for Accessibility Options.'; const challengeSolution = '// This is in-line comment'; - await page.getByLabel(editorPaneLabel).fill(challengeSolution); + await getEditors(page).fill(challengeSolution); const submitButton = page.getByRole('button', { name: isMobile ? translations.buttons.run : translations.buttons['run-test'] diff --git a/e2e/shortcuts-modal.spec.ts b/e2e/shortcuts-modal.spec.ts index 51f76854b0a..7df65f74c90 100644 --- a/e2e/shortcuts-modal.spec.ts +++ b/e2e/shortcuts-modal.spec.ts @@ -2,11 +2,10 @@ import { APIRequestContext, Page, expect, test } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; import { authedPut } from './utils/request'; +import { getEditors } from './utils/editor'; const course = '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code'; -const editorPaneLabel = - 'Editor content;Press Alt+F1 for Accessibility Options.'; test.use({ storageState: 'playwright/.auth/certified-user.json' }); @@ -23,7 +22,7 @@ const enableKeyboardShortcuts = async (request: APIRequestContext) => { const openModal = async (page: Page) => { // The editor pane is focused by default, so we need to escape or it will // capture the keyboard shortcuts - await page.getByLabel(editorPaneLabel).press('Escape'); + await getEditors(page).press('Escape'); await page.keyboard.press('Shift+?'); }; diff --git a/e2e/utils/editor.ts b/e2e/utils/editor.ts new file mode 100644 index 00000000000..17b8a09f2d0 --- /dev/null +++ b/e2e/utils/editor.ts @@ -0,0 +1,48 @@ +import { type Page } from '@playwright/test'; + +/** + * Retrieves any editor elements on the page. + * @param page - The Playwright page object. + * @returns The editor elements. + */ +export function getEditors(page: Page) { + return page.getByLabel( + 'Editor content;Press Alt+F1 for Accessibility Options' + ); +} + +export async function focusEditor({ + page, + isMobile, + browserName +}: { + page: Page; + isMobile: boolean; + browserName: string; +}) { + // The editor has an overlay div, which prevents the click event from bubbling up in iOS Safari. + // This is a quirk in this browser-OS combination, and the workaround here is to use `.focus()` + // in place of `.click()` to focus on the editor. + // Ref: https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if (isMobile && browserName === 'webkit') { + await getEditors(page).focus(); + } else { + await getEditors(page).click(); + } +} + +export async function clearEditor({ + page, + browserName +}: { + page: Page; + browserName: string; +}) { + // TODO: replace with ControlOrMeta when it's supported + if (browserName === 'webkit') { + await page.keyboard.press('Meta+a'); + } else { + await page.keyboard.press('Control+a'); + } + await page.keyboard.press('Backspace'); +}