From ffb105d7371237e36c5b75eac4ba45ff91b29867 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 10 Sep 2020 13:00:16 +0200 Subject: [PATCH] chore: refactor E2E tests (#14935) * chore: refactor E2E tests * fix description * fix slowMo val * fix descriptions * one more fix * refactor pressKey * refactor getPropertyValue * rename methods --- packages/fluentui/e2e/e2eApi.ts | 120 +++++++++---- packages/fluentui/e2e/setup.test.ts | 4 +- .../fluentui/e2e/tests/datepicker-test.ts | 93 +++++----- .../e2e/tests/dialogInDialog-example.tsx | 45 +++-- .../fluentui/e2e/tests/dialogInDialog-test.ts | 54 +++--- .../tests/dialogInDialogWithDropdown-test.ts | 27 +-- .../e2e/tests/dialogInPopup-example.tsx | 45 +++-- .../fluentui/e2e/tests/dialogInPopup-test.ts | 42 ++--- .../e2e/tests/dialogPreventScroll-test.ts | 29 ++-- .../e2e/tests/dialogWithDropdown-example.tsx | 8 +- .../e2e/tests/dialogWithDropdown-test.ts | 44 ++--- packages/fluentui/e2e/tests/dropdown-test.ts | 14 +- .../e2e/tests/dropdownMoveFocusOnTab-test.ts | 22 ++- .../fluentui/e2e/tests/dropdownSearch-test.ts | 22 +-- .../e2e/tests/menuDismissScroll-test.ts | 10 +- .../e2e/tests/popupClickHandling-test.ts | 6 +- .../e2e/tests/popupEscHandling-example.tsx | 1 + .../e2e/tests/popupEscHandling-test.ts | 10 +- .../fluentui/e2e/tests/popupInMenu-test.ts | 32 ++-- .../fluentui/e2e/tests/popupInPopup-test.ts | 20 +-- .../tests/popupInSubmenuInToolbarMenu-test.ts | 67 +++----- .../e2e/tests/popupInToolbarMenu-test.ts | 51 +++--- .../e2e/tests/popupWithCloseInContent-test.ts | 18 +- .../e2e/tests/popupWithoutTrigger-test.ts | 14 +- .../e2e/tests/submenuInToolbarMenu-test.ts | 46 +++-- .../fluentui/e2e/tests/tableNavigable-test.ts | 160 +++++++++--------- .../fluentui/e2e/tests/toolbarMenu-test.ts | 32 ++-- .../e2e/tests/toolbarMenuOverflow-test.ts | 20 +-- .../tests/toolbarMenuOverflowWrapped-test.ts | 8 +- scripts/gulp/plugins/gulp-jest.ts | 2 + scripts/webpack/webpack.config.e2e.ts | 2 +- 31 files changed, 571 insertions(+), 497 deletions(-) diff --git a/packages/fluentui/e2e/e2eApi.ts b/packages/fluentui/e2e/e2eApi.ts index 17fabe454dbab..b82678640f5bb 100644 --- a/packages/fluentui/e2e/e2eApi.ts +++ b/packages/fluentui/e2e/e2eApi.ts @@ -14,6 +14,23 @@ export const exampleUrlTokenFromFilePath = (filePath: string) => { return _.kebabCase(testName); }; +type E2EKeys = + | 'ArrowDown' + | 'ArrowLeft' + | 'ArrowRight' + | 'ArrowUp' + | 'End' + | 'Enter' + | 'Escape' + | 'Home' + | 'PageDown' + | 'PageUp' + | 'Tab' + | 'F' + | 'O'; + +const PUPPETEER_ACTION_TIMEOUT = 10 * 1000; + export class E2EApi { constructor(private readonly page: Page) {} @@ -28,54 +45,99 @@ export class E2EApi { }; public getElement = async (selector: string) => { - return await this.page.waitForSelector(selector, { timeout: 10 * 1000 }); + return await this.page.waitForSelector(selector, { timeout: PUPPETEER_ACTION_TIMEOUT }); }; public hover = async (selector: string) => { return await this.page.hover(selector); }; - public getAttributeValue = async (selector: string, attr) => { - return this.page.$eval(selector, (el, attribute) => el.getAttribute(attribute), attr); + public hasComputedStyle = async ( + selector: string, + property: keyof CSSStyleDeclaration, + value: string, + ): Promise => { + await this.page.waitForFunction( + (selector, property, value) => { + return window.getComputedStyle(document.querySelector(selector))[property] === value; + }, + { timeout: PUPPETEER_ACTION_TIMEOUT }, + selector, + property, + value, + ); }; - public getPropertyValue = async (selector: string, prop) => { - return this.page.$eval(selector, (el, prop) => el[prop], prop); + public hasPropertyValue = async (selector: string, property: string, value: number | string): Promise => { + await this.page.waitForFunction( + (selector, property, value) => { + return document.querySelector(selector)[property] === value; + }, + { timeout: PUPPETEER_ACTION_TIMEOUT }, + selector, + property, + value, + ); }; - public count = async (selector: string) => { - return (await this.page.$$(selector)).length; + public expectCount = async (selector: string, count: number): Promise => { + await this.page.waitForFunction( + (selector, count) => { + return document.querySelectorAll(selector).length === count; + }, + { timeout: PUPPETEER_ACTION_TIMEOUT }, + selector, + count, + ); }; - public exists = async (selector: string) => { - return (await this.count(selector)) > 0; + public exists = async (selector: string): Promise => { + await this.page.waitForSelector(selector, { timeout: PUPPETEER_ACTION_TIMEOUT }); }; - public clickOn = async (selector: string) => await (await this.getElement(selector)).click(); - - public clickOnPosition = async (selector: string, x: number, y: number) => { - const elementHandle = await this.getElement(selector); - const boundingBox = await elementHandle.boundingBox(); + public expectHidden = async (selector: string): Promise => { + await this.page.waitForSelector(selector, { hidden: true, timeout: PUPPETEER_ACTION_TIMEOUT }); + }; - await this.page.mouse.click(Math.round(boundingBox.x) + x, Math.round(boundingBox.y) + y); + public clickOn = async (selector: string): Promise => { + await (await this.getElement(selector)).click(); }; - public textOf = async (selector: string) => { - const element = await this.getElement(selector); - return await (await element.getProperty('textContent')).jsonValue(); + public expectTextOf = async (selector: string, text: string): Promise => { + await this.page.waitForFunction( + (selector, text) => { + return document.querySelector(selector).innerText === text; + }, + { timeout: PUPPETEER_ACTION_TIMEOUT }, + selector, + text, + ); }; - public focusOn = async (selector: string) => await (await this.getElement(selector)).focus(); + public focusOn = async (selector: string): Promise => { + await (await this.getElement(selector)).focus(); + }; - public isFocused = async (selector: string) => - await this.page.evaluate(selector => { - const activeElement = document.activeElement; - const selectorElement = document.querySelector(selector); + public isFocused = async (selector: string): Promise => { + await this.page.waitForFunction( + selector => { + const activeElement = document.activeElement; + const selectorElement = document.querySelector(selector); + + return activeElement === selectorElement; + }, + { timeout: PUPPETEER_ACTION_TIMEOUT }, + selector, + ); + }; - return activeElement === selectorElement; - }, selector); + public waitForSelectorAndPressKey = async ( + selector: string, + key: E2EKeys, + modifier?: 'Control' | 'Shift', + ): Promise => { + await this.getElement(selector); - public pressKey = async (key: string, modifier?: string) => { if (modifier) { await this.page.keyboard.down(modifier); } @@ -87,13 +149,13 @@ export class E2EApi { } }; - public resizeViewport = async (size: Partial) => { + public resizeViewport = async (size: Partial): Promise => { const { height, width } = this.page.viewport(); await this.page.setViewport({ height, width, ...size }); }; // Once we update puppeteer we should replace this by using https://pptr.dev/#?product=Puppeteer&version=v5.2.1&show=api-mousewheeloptions - public simulatePageMove = async () => { + public simulatePageMove = async (): Promise => { await this.page.evaluate(_ => { const type = 'move'; const event = document.createEvent('Event') as TouchEvent; @@ -103,5 +165,5 @@ export class E2EApi { }); }; - public wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + public wait = async (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/packages/fluentui/e2e/setup.test.ts b/packages/fluentui/e2e/setup.test.ts index 36ca4940e4da1..84bcac66bf6e7 100644 --- a/packages/fluentui/e2e/setup.test.ts +++ b/packages/fluentui/e2e/setup.test.ts @@ -3,7 +3,7 @@ import puppeteer from 'puppeteer'; import { safeLaunchOptions } from '@uifabric/build/puppeteer/puppeteer.config'; import { E2EApi } from './e2eApi'; -jest.setTimeout(10000); +jest.setTimeout(30000); let browser: puppeteer.Browser; let page: puppeteer.Page; @@ -12,7 +12,7 @@ let consoleErrors: string[] = []; const launchOptions: puppeteer.LaunchOptions = safeLaunchOptions({ headless: true, dumpio: false, - slowMo: 10, + slowMo: 0, }); beforeAll(async () => { diff --git a/packages/fluentui/e2e/tests/datepicker-test.ts b/packages/fluentui/e2e/tests/datepicker-test.ts index 5cebe52dab6fa..809e2a7c36288 100644 --- a/packages/fluentui/e2e/tests/datepicker-test.ts +++ b/packages/fluentui/e2e/tests/datepicker-test.ts @@ -13,66 +13,69 @@ describe('Datepicker', () => { it('Click to the button should open calendar', async () => { await e2e.clickOn(datepickerButton); - expect(await e2e.exists(datepickerCalendar)).toBe(true); + await e2e.exists(datepickerCalendar); }); it('Clicking arrow left on the first visible element of the grid should change month', async () => { await e2e.focusOn(datepickerButton); - await e2e.pressKey('Enter'); // open calendar - expect(await e2e.exists(datepickerCalendar)).toBe(true); - expect(await e2e.isFocused(datepickerCalendarCell(32))).toBe(true); // 32 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(32))).toBe('23'); // which represents July 23, 2020, cell focused by default + await e2e.waitForSelectorAndPressKey(datepickerButton, 'Enter'); // open calendar + await e2e.exists(datepickerCalendar); + + await e2e.isFocused(datepickerCalendarCell(32)); // 32 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(32), '23'); // which represents July 23, 2020, cell focused by default await e2e.focusOn(datepickerCalendarCell(8)); // 8 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(8))).toBe('29'); // which represents June 29, 2020 the first visible cell value + await e2e.expectTextOf(datepickerCalendarCell(8), '29'); // which represents June 29, 2020 the first visible cell value - await e2e.pressKey('ArrowLeft'); - expect(await e2e.isFocused(datepickerCalendarCell(35))).toBe(true); // 35 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(35))).toBe('28'); // which represents June 28, 2020, cell which should be focused on after the grid update + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'ArrowLeft'); + await e2e.isFocused(datepickerCalendarCell(35)); // 35 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(35), '28'); // which represents June 28, 2020, cell which should be focused on after the grid update }); it('Clicking arrow right on the last visible element of the grid should change month', async () => { await e2e.focusOn(datepickerButton); - await e2e.pressKey('Enter'); // open calendar - expect(await e2e.exists(datepickerCalendar)).toBe(true); - expect(await e2e.isFocused(datepickerCalendarCell(32))).toBe(true); // 32 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(32))).toBe('23'); // which represents July 23, 2020, cell focused by default + await e2e.waitForSelectorAndPressKey(datepickerButton, 'Enter'); // open calendar + await e2e.exists(datepickerCalendar); + + await e2e.isFocused(datepickerCalendarCell(32)); // 32 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(32), '23'); // which represents July 23, 2020, cell focused by default await e2e.focusOn(datepickerCalendarCell(42)); // 42 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(42))).toBe('2'); // which represents August 2, 2020 the last visible cell value + await e2e.expectTextOf(datepickerCalendarCell(42), '2'); // which represents August 2, 2020 the last visible cell value - await e2e.pressKey('ArrowRight'); - expect(await e2e.isFocused(datepickerCalendarCell(15))).toBe(true); // 15 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(15))).toBe('3'); // which represents August 3, 2020, cell which should be focused on after the grid update + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'ArrowRight'); + await e2e.isFocused(datepickerCalendarCell(15)); // 15 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(15), '3'); // which represents August 3, 2020, cell which should be focused on after the grid update }); it('Advanced keyboard navigation works', async () => { await e2e.focusOn(datepickerButton); - await e2e.pressKey('Enter'); // open calendar - expect(await e2e.exists(datepickerCalendar)).toBe(true); - expect(await e2e.isFocused(datepickerCalendarCell(32))).toBe(true); // 32 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(32))).toBe('23'); // which represents July 23, 2020, cell focused by default - - await e2e.pressKey('Home'); - expect(await e2e.isFocused(datepickerCalendarCell(29))).toBe(true); // 29 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(29))).toBe('20'); // which represents July 20, 2020, first cell in the same grid row - - await e2e.pressKey('End'); - expect(await e2e.isFocused(datepickerCalendarCell(35))).toBe(true); // 35 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(35))).toBe('26'); // which represents July 26, 2020, last cell in the same grid row - - await e2e.pressKey('PageUp'); - expect(await e2e.isFocused(datepickerCalendarCell(14))).toBe(true); // 14 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(14))).toBe('5'); // which represents July 5, 2020, first cell in the same grid column - - await e2e.pressKey('PageDown'); - expect(await e2e.isFocused(datepickerCalendarCell(42))).toBe(true); // 42 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(42))).toBe('2'); // which represents August 2, 2020, last cell in the same grid column - - await e2e.pressKey('Home', 'Control'); - expect(await e2e.isFocused(datepickerCalendarCell(8))).toBe(true); // 8 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(8))).toBe('29'); // which represents June 29, 2020, first cell in the grid - - await e2e.pressKey('End', 'Control'); - expect(await e2e.isFocused(datepickerCalendarCell(42))).toBe(true); // 42 is a magic number - expect(await e2e.textOf(datepickerCalendarCell(42))).toBe('2'); // which represents August 2, 2020, last cell in the grid + await e2e.waitForSelectorAndPressKey(datepickerButton, 'Enter'); // open calendar + await e2e.exists(datepickerCalendar); + + await e2e.isFocused(datepickerCalendarCell(32)); // 32 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(32), '23'); // which represents July 23, 2020, cell focused by default + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'Home'); + await e2e.isFocused(datepickerCalendarCell(29)); // 29 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(29), '20'); // which represents July 20, 2020, first cell in the same grid row + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'End'); + await e2e.isFocused(datepickerCalendarCell(35)); // 35 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(35), '26'); // which represents July 26, 2020, last cell in the same grid row + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'PageUp'); + await e2e.isFocused(datepickerCalendarCell(14)); // 14 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(14), '5'); // which represents July 5, 2020, first cell in the same grid column + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'PageDown'); + await e2e.isFocused(datepickerCalendarCell(42)); // 42 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(42), '2'); // which represents August 2, 2020, last cell in the same grid column + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'Home', 'Control'); + await e2e.isFocused(datepickerCalendarCell(8)); // 8 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(8), '29'); // which represents June 29, 2020, first cell in the grid + + await e2e.waitForSelectorAndPressKey(datepickerCalendar, 'End', 'Control'); + await e2e.isFocused(datepickerCalendarCell(42)); // 42 is a magic number + await e2e.expectTextOf(datepickerCalendarCell(42), '2'); // which represents August 2, 2020, last cell in the grid }); }); diff --git a/packages/fluentui/e2e/tests/dialogInDialog-example.tsx b/packages/fluentui/e2e/tests/dialogInDialog-example.tsx index a0a3596f1e6a4..6a3ff6b033dc6 100644 --- a/packages/fluentui/e2e/tests/dialogInDialog-example.tsx +++ b/packages/fluentui/e2e/tests/dialogInDialog-example.tsx @@ -5,29 +5,42 @@ export const selectors = { outerClose: 'outer-close', outerHeader: 'outer-header', outerTrigger: 'outer-trigger', - outerOverlay: 'outer-overlay', innerClose: 'inner-close', innerHeader: 'inner-header', innerTrigger: 'inner-trigger', - innerOverlay: 'inner-overlay', + + overlayPoint: 'overlay-point', }; const DialogInPopupExample = () => ( - } - /> - } - header={{ content: 'An outer', id: selectors.outerHeader }} - overlay={{ id: selectors.outerOverlay }} - trigger={