diff --git a/core/src/utils/test/playwright/page/event-spy.ts b/core/src/utils/test/playwright/page/event-spy.ts index 5d134f0e95..4d7979a74a 100644 --- a/core/src/utils/test/playwright/page/event-spy.ts +++ b/core/src/utils/test/playwright/page/event-spy.ts @@ -1,3 +1,5 @@ +import type { JSHandle } from '@playwright/test'; + import type { E2EPage } from '../playwright-declarations'; /** @@ -90,15 +92,20 @@ export const initPageEvents = async (page: E2EPage) => { * page context to updates the _e2eEvents map * when an event is fired. */ -export const addE2EListener = async (page: E2EPage, eventName: string, callback: (ev: any) => void) => { +export const addE2EListener = async ( + page: E2EPage, + elmHandle: JSHandle, + eventName: string, + callback: (ev: any) => void +) => { const id = page._e2eEventsIds++; page._e2eEvents.set(id, { eventName, callback, }); - await page.evaluate( - ([eventName, id]) => { + await elmHandle.evaluate( + (elm, [eventName, id]) => { (window as any).stencilSerializeEventTarget = (target: any) => { // BROWSER CONTEXT if (!target) { @@ -146,7 +153,7 @@ export const addE2EListener = async (page: E2EPage, eventName: string, callback: return serializedEvent; }; - window.addEventListener(eventName as string, (ev: Event) => { + elm.addEventListener(eventName as string, (ev: Event) => { (window as any).ionicOnEvent(id, (window as any).serializeStencilEvent(ev)); }); }, diff --git a/core/src/utils/test/playwright/page/utils/index.ts b/core/src/utils/test/playwright/page/utils/index.ts index 4bef8cfc41..d5fad6cc12 100644 --- a/core/src/utils/test/playwright/page/utils/index.ts +++ b/core/src/utils/test/playwright/page/utils/index.ts @@ -4,3 +4,4 @@ export * from './get-snapshot-settings'; export * from './set-ion-viewport'; export * from './spy-on-event'; export * from './set-content'; +export * from './locator'; diff --git a/core/src/utils/test/playwright/page/utils/locator.ts b/core/src/utils/test/playwright/page/utils/locator.ts new file mode 100644 index 0000000000..9088d7ca7f --- /dev/null +++ b/core/src/utils/test/playwright/page/utils/locator.ts @@ -0,0 +1,41 @@ +import type { Locator } from '@playwright/test'; + +import type { E2EPage } from '../../playwright-declarations'; +import { EventSpy, addE2EListener } from '../event-spy'; + +export type LocatorOptions = { + hasText?: string | RegExp; + has?: Locator; +}; + +export interface E2ELocator extends Locator { + /** + * Creates a new EventSpy and listens + * on the element for an event. + * The test will timeout if the event + * never fires. + * + * Usage: + * const input = page.locator('ion-input'); + * const ionChange = await locator.spyOnEvent('ionChange'); + * ... + * await ionChange.next(); + */ + spyOnEvent: (eventName: string) => void; +} + +export const locator = ( + page: E2EPage, + originalFn: typeof page.locator, + selector: string, + options?: LocatorOptions +): E2ELocator => { + const locator = originalFn(selector, options) as E2ELocator; + locator.spyOnEvent = async (eventName: string) => { + const spy = new EventSpy(eventName); + const handle = await locator.evaluateHandle((node: HTMLElement) => node); + await addE2EListener(page, handle, eventName, (ev: CustomEvent) => spy.push(ev)); + return spy; + }; + return locator; +}; diff --git a/core/src/utils/test/playwright/page/utils/spy-on-event.ts b/core/src/utils/test/playwright/page/utils/spy-on-event.ts index c205a0e9c9..a34f719402 100644 --- a/core/src/utils/test/playwright/page/utils/spy-on-event.ts +++ b/core/src/utils/test/playwright/page/utils/spy-on-event.ts @@ -4,7 +4,9 @@ import { addE2EListener, EventSpy } from '../event-spy'; export const spyOnEvent = async (page: E2EPage, eventName: string): Promise => { const spy = new EventSpy(eventName); - await addE2EListener(page, eventName, (ev: CustomEvent) => spy.push(ev)); + const handle = await page.evaluateHandle(() => window); + + await addE2EListener(page, handle, eventName, (ev: CustomEvent) => spy.push(ev)); return spy; }; diff --git a/core/src/utils/test/playwright/playwright-declarations.ts b/core/src/utils/test/playwright/playwright-declarations.ts index 11a93886d0..0f56d701c4 100644 --- a/core/src/utils/test/playwright/playwright-declarations.ts +++ b/core/src/utils/test/playwright/playwright-declarations.ts @@ -1,6 +1,7 @@ import type { Page, Response } from '@playwright/test'; import type { EventSpy } from './page/event-spy'; +import type { LocatorOptions, E2ELocator } from './page/utils/locator'; export interface E2EPage extends Page { /** @@ -28,6 +29,13 @@ export interface E2EPage extends Page { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. */ goto: (url: string) => Promise; + + /** + * Find an element by selector. + * See https://playwright.dev/docs/locators for more information. + */ + locator: (selector: string, options?: LocatorOptions) => E2ELocator; + /** * Increases the size of the page viewport to match the `ion-content` contents. * Use this method when taking full-screen screenshots. diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index 2cfa61147c..5563aeca97 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -15,7 +15,9 @@ import { setIonViewport, spyOnEvent, waitForChanges, + locator, } from './page/utils'; +import type { LocatorOptions } from './page/utils'; import type { E2EPage } from './playwright-declarations'; type CustomTestArgs = PlaywrightTestArgs & @@ -32,10 +34,12 @@ type CustomFixtures = { export const test = base.extend({ page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { const originalGoto = page.goto.bind(page); + const originalLocator = page.locator.bind(page); // Overridden Playwright methods page.goto = (url: string) => goToPage(page, url, testInfo, originalGoto); page.setContent = (html: string) => setContent(page, html); + page.locator = (selector: string, options?: LocatorOptions) => locator(page, originalLocator, selector, options); // Custom Ionic methods page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); page.setIonViewport = () => setIonViewport(page);