mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Merge remote-tracking branch 'origin/main' into sync-main-playwright
This commit is contained in:
100
core/src/utils/test/playwright/eventSpy.ts
Normal file
100
core/src/utils/test/playwright/eventSpy.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { IonicPage } from './fixtures';
|
||||
|
||||
/**
|
||||
* The EventSpy class allows
|
||||
* developers to listen for
|
||||
* a particular event emission and
|
||||
* pass/fail the test based on whether
|
||||
* or not the event was emitted.
|
||||
* Based off https://github.com/ionic-team/stencil/blob/16b8ea4dabb22024872a38bc58ba1dcf1c7cc25b/src/testing/puppeteer/puppeteer-events.ts#L64
|
||||
*/
|
||||
export class EventSpy {
|
||||
/**
|
||||
* Keeping track of a cursor
|
||||
* ensures that no two spy.next()
|
||||
* calls point to the same event.
|
||||
*/
|
||||
private cursor = 0;
|
||||
private queuedHandler: (() => void)[] = [];
|
||||
public events: Event[] = [];
|
||||
|
||||
constructor(public eventName: string) {}
|
||||
|
||||
get length() {
|
||||
return this.events.length;
|
||||
}
|
||||
|
||||
public next() {
|
||||
const { cursor } = this;
|
||||
this.cursor++;
|
||||
|
||||
const next = this.events[cursor];
|
||||
if (next) {
|
||||
return Promise.resolve(next);
|
||||
} else {
|
||||
/**
|
||||
* If the event has not already been
|
||||
* emitted, then add it to the queuedHandler.
|
||||
* When the event is emitted, the push
|
||||
* method is called which results in the
|
||||
* Promise below being resolved.
|
||||
*/
|
||||
let resolve: () => void;
|
||||
const promise = new Promise<void>((r) => (resolve = r));
|
||||
// @ts-ignore
|
||||
this.queuedHandler.push(resolve);
|
||||
|
||||
return promise.then(() => this.events[cursor]);
|
||||
}
|
||||
}
|
||||
|
||||
public push(ev: Event) {
|
||||
this.events.push(ev);
|
||||
const next = this.queuedHandler.shift();
|
||||
if (next) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes information required to
|
||||
* spy on events.
|
||||
* The ionicOnEvent function is called in the
|
||||
* context of the current page. This lets us
|
||||
* respond to an event listener created within
|
||||
* the page itself.
|
||||
*/
|
||||
export const initPageEvents = async (page: IonicPage) => {
|
||||
page._e2eEventsIds = 0;
|
||||
page._e2eEvents = new Map();
|
||||
|
||||
await page.exposeFunction('ionicOnEvent', (id: number, ev: any) => {
|
||||
const context = page._e2eEvents.get(id);
|
||||
if (context) {
|
||||
context.callback(ev);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new event listener in the current
|
||||
* page context to updates the _e2eEvents map
|
||||
* when an event is fired.
|
||||
*/
|
||||
export const addE2EListener = async (page: IonicPage, eventName: string, callback: (ev: any) => void) => {
|
||||
const id = page._e2eEventsIds++;
|
||||
page._e2eEvents.set(id, {
|
||||
eventName,
|
||||
callback,
|
||||
});
|
||||
|
||||
await page.evaluate(
|
||||
([eventName, id]) => {
|
||||
window.addEventListener(eventName as string, (ev: Event) => {
|
||||
(window as any).ionicOnEvent(id, ev);
|
||||
});
|
||||
},
|
||||
[eventName, id]
|
||||
);
|
||||
};
|
||||
@@ -9,10 +9,15 @@ import type {
|
||||
} from '@playwright/test';
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
type IonicPage = Page & {
|
||||
import { EventSpy, initPageEvents, addE2EListener } from './eventSpy';
|
||||
|
||||
export type IonicPage = Page & {
|
||||
goto: (url: string) => Promise<null | Response>;
|
||||
setIonViewport: () => Promise<void>;
|
||||
getSnapshotSettings: () => string;
|
||||
spyOnEvent: (eventName: string) => Promise<EventSpy>;
|
||||
_e2eEventsIds: number;
|
||||
_e2eEvents: Map<number, any>;
|
||||
};
|
||||
|
||||
type CustomTestArgs = PlaywrightTestArgs &
|
||||
@@ -124,6 +129,27 @@ export const test = base.extend<CustomFixtures>({
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new EventSpy and listens
|
||||
* on the window for an event.
|
||||
* The test will timeout if the event
|
||||
* never fires.
|
||||
*
|
||||
* Usage:
|
||||
* const ionChange = await page.spyOnEvent('ionChange');
|
||||
* ...
|
||||
* await ionChange.next();
|
||||
*/
|
||||
page.spyOnEvent = async (eventName: string): Promise<EventSpy> => {
|
||||
const spy = new EventSpy(eventName);
|
||||
|
||||
await addE2EListener(page, eventName, (ev: Event) => spy.push(ev));
|
||||
|
||||
return spy;
|
||||
};
|
||||
|
||||
initPageEvents(page);
|
||||
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
2
core/src/utils/test/playwright/index.ts
Normal file
2
core/src/utils/test/playwright/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './fixtures';
|
||||
export * from './eventSpy';
|
||||
Reference in New Issue
Block a user