Merge remote-tracking branch 'origin/main' into sync-main-playwright

This commit is contained in:
Liam DeBeasi
2022-04-08 12:13:16 -04:00
29 changed files with 1198 additions and 37 deletions

View 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]
);
};

View File

@@ -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);
},
});

View File

@@ -0,0 +1,2 @@
export * from './fixtures';
export * from './eventSpy';