Skip to content

Commit

Permalink
chore: refactor E2E tests (#14935)
Browse files Browse the repository at this point in the history
* chore: refactor E2E tests

* fix description

* fix slowMo val

* fix descriptions

* one more fix

* refactor pressKey

* refactor getPropertyValue

* rename methods
  • Loading branch information
layershifter authored Sep 10, 2020
1 parent df56f46 commit ffb105d
Show file tree
Hide file tree
Showing 31 changed files with 571 additions and 497 deletions.
120 changes: 91 additions & 29 deletions packages/fluentui/e2e/e2eApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand All @@ -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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
await this.getElement(selector);

public pressKey = async (key: string, modifier?: string) => {
if (modifier) {
await this.page.keyboard.down(modifier);
}
Expand All @@ -87,13 +149,13 @@ export class E2EApi {
}
};

public resizeViewport = async (size: Partial<Viewport>) => {
public resizeViewport = async (size: Partial<Viewport>): Promise<void> => {
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<void> => {
await this.page.evaluate(_ => {
const type = 'move';
const event = document.createEvent('Event') as TouchEvent;
Expand All @@ -103,5 +165,5 @@ export class E2EApi {
});
};

public wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
public wait = async (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
}
4 changes: 2 additions & 2 deletions packages/fluentui/e2e/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,7 +12,7 @@ let consoleErrors: string[] = [];
const launchOptions: puppeteer.LaunchOptions = safeLaunchOptions({
headless: true,
dumpio: false,
slowMo: 10,
slowMo: 0,
});

beforeAll(async () => {
Expand Down
93 changes: 48 additions & 45 deletions packages/fluentui/e2e/tests/datepicker-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});
45 changes: 29 additions & 16 deletions packages/fluentui/e2e/tests/dialogInDialog-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => (
<Dialog
cancelButton={{ content: 'Close', id: selectors.outerClose }}
content={
<Dialog
cancelButton={{ content: 'Close', id: selectors.innerClose }}
header={{ content: 'An inner', id: selectors.innerHeader }}
overlay={{ id: selectors.innerOverlay }}
trigger={<Button id={selectors.innerTrigger} content="Open a dialog" />}
/>
}
header={{ content: 'An outer', id: selectors.outerHeader }}
overlay={{ id: selectors.outerOverlay }}
trigger={<Button id={selectors.outerTrigger} content="Open a dialog" />}
/>
<>
<div
id={selectors.overlayPoint}
style={{
pointerEvents: 'none',
position: 'fixed',
top: 10,
left: 10,
border: '3px solid green',
zIndex: 1000000,
}}
>
Overlay close
</div>
<Dialog
cancelButton={{ content: 'Close', id: selectors.outerClose }}
content={
<Dialog
cancelButton={{ content: 'Close', id: selectors.innerClose }}
header={{ content: 'An inner', id: selectors.innerHeader }}
trigger={<Button id={selectors.innerTrigger} content="Open a dialog" />}
/>
}
header={{ content: 'An outer', id: selectors.outerHeader }}
trigger={<Button id={selectors.outerTrigger} content="Open a dialog" />}
/>
</>
);

export default DialogInPopupExample;
Loading

0 comments on commit ffb105d

Please sign in to comment.