8000 Cordova integration tests by eudamniac · Pull Request #216 · keycloak/keycloak-js · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions test/support/cordova.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { BrowserContext, JSHandle, Page } from "@playwright/test";
import { createTestBed } from "./testbed.ts";

export const CORDOVA_REDIRECT_URL = new URL("http://localhost");

export async function setupCordovaMock(
page: Page,
appUrl: URL,
authServerUrl: URL,
options?: {
onInAppBrowserRedirect?: (refHandle: JSHandle<any>) => Promise<void>;
}
) {
const browserContext = page.context();
const refHandle = await getInAppBrowserRefMock(page);
setupEventTriggerOnNewPageForInAppBrowserRef(
browserContext,
page,
refHandle,
appUrl,
authServerUrl,
options
);
await injectCordovaInAppBrowserMock(page, refHandle);
return refHandle;
}

export async function getInAppBrowserRefMock(page: Page) {
return page.evaluateHandle(() => {
const eventListeners: { [key: string]: Function[] } = {};
let options: string | undefined;
return {
addEventListener: (event: string, callback: Function) => {
if (!eventListeners[event]) eventListeners[event] = [];
eventListeners[event].push(callback);
},
close: async () => {
await (window as any).closeInAppBrowserPage();
eventListeners["exit"]?.forEach((cb) => cb({}));
},
_triggerEvent: (event: string, data: any) => {
eventListeners[event]?.forEach((cb) => cb(data));
},
_captureOptions: (opts: any) => {
options = opts;
},
_getCapturedOptions: () => {
return options;
},
_reset: () => {
options = undefined;
for (const key in eventListeners) {
delete eventListeners[key];
}
},
};
});
}

export async function injectCordovaInAppBrowserMock(
page: Page,
refHandle: JSHandle<{
addEventListener: (event: string, callback: Function) => void;
close: () => void;
_triggerEvent: (event: string, data: any) => void;
_captureOptions: (opts: any) => void;
_reset: () => void;
}>
) {
await page.evaluate((ref) => {
(window as any).cordova = {
InAppBrowser: {
open: (url: string, target: string, options: string) => {
ref._reset();
window.open(url, target, options);
ref._captureOptions(options);
return ref;
},
},
};
}, refHandle);
}

export async function setupEventTriggerOnNewPageForInAppBrowserRef(
browserContext: BrowserContext,
page: Page,
refHandle: JSHandle<any>,
appUrl: URL,
authServerUrl: URL,
options?: {
onInAppBrowserRedirect?: (refHandle: JSHandle<any>) => Promise<void>;
}
) {
let currentlyOpenPage: Page | null = null;
browserContext.on("page", async (newPage) => {
currentlyOpenPage = newPage;
await newPage.waitForLoadState();
const { executor: newExecutor } = await createTestBed(newPage, {
appUrl,
authServerUrl,
});
const loginLocator = await newPage.getByRole("textbox", {
name: "Username or email",
});
if (await loginLocator.isVisible()) {
await newExecutor.submitLoginForm();
}
const pageUrl = newPage.url();

await 8000 refHandle.evaluate(
(ref, { pageUrl }) => {
ref._triggerEvent("loadstart", { url: pageUrl });
},
{ pageUrl }
);

if (options?.onInAppBrowserRedirect) {
await options.onInAppBrowserRedirect(refHandle);
return;
}
});

await page.exposeFunction("closeInAppBrowserPage", async () => {
if (currentlyOpenPage && !currentlyOpenPage.isClosed()) {
await currentlyOpenPage.close();
}
});
}
15 changes: 13 additions & 2 deletions test/support/test-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ export class TestExecutor {
}
}

cordovaInitOptions (): KeycloakInitOptions {
return {
...this.defaultInitOptions(),
adapter: 'cordova',
// cordova adapter has 'http://localhost' as default redirectUri which wouldn't work unless we start another local app
redirectUri: this.#options.appUrl.toString(),
}
}

silentSSORedirectUrl (): URL {
return new URL('./silent-check-sso.html', this.#options.appUrl)
}
Expand Down Expand Up @@ -177,7 +186,7 @@ export class TestExecutor {
}
}

async logout (options?: KeycloakLogoutOptions): Promise<void> {
async logout (options?: KeycloakLogoutOptions, skipWaitForNavigation?: boolean): Promise<void> {
await this.#assertInstantiated()

let result
Expand All @@ -202,7 +211,9 @@ export class TestExecutor {
throw result.error as Error
}

await this.#page.waitForNavigation()
if (!skipWaitForNavigation) {
await this.#page.waitForNavigation()
}
}

async updateToken (minValidity?: number): Promise<boolean> {
Expand Down
73 changes: 73 additions & 0 deletions test/tests/cordova-adapter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect } from "@playwright/test";
import { createTestBed, test } from "../support/testbed.ts";
import { setupCordovaMock } from "../support/cordova.ts";

test.describe("Cordova adapter", () => {
test("initializes with cordova adapter", async ({
page,
appUrl,
authServerUrl,
}) => {
const { executor } = await createTestBed(page, { appUrl, authServerUrl });
const initOptions = executor.cordovaInitOptions();
await executor.navigateToApp();
expect(await executor.initializeAdapter(initOptions)).toBe(false);
expect(await executor.isAuthenticated()).toBe(false);
});

test("logs in with cordova adapter and InAppBrowser", async ({
page,
appUrl,
authServerUrl,
}) => {
const { executor } = await createTestBed(page, { appUrl, authServerUrl });
const initOptions = executor.cordovaInitOptions();
await executor.navigateToApp();
expect(await executor.initializeAdapter(initOptions)).toBe(false);
await setupCordovaMock(page, appUrl, authServerUrl);

const loginPromise = executor.login();
await expect(loginPromise).resolves.toBeUndefined();
expect(await executor.isAuthenticated()).toBe(true);
expect(page.context().pages().length).toBe(1);
});

test("should pass location=no option by default to InAppBrowser", async ({
page,
appUrl,
authServerUrl,
}) => {
const { executor } = await createTestBed(page, { appUrl, authServerUrl });
const initOptions = executor.cordovaInitOptions();
await executor.navigateToApp();
expect(await executor.initializeAdapter(initOptions)).toBe(false);
const refHandle = await setupCordovaMock(page, appUrl, authServerUrl);

const loginPromise = executor.login();
await expect(loginPromise).resolves.toBeUndefined();
const options = await refHandle.evaluate((ref) =>
ref._getCapturedOptions()
);
expect(options).toBe("location=no");
});

test("handles cordova adapter logout", async ({
page,
appUrl,
authServerUrl,
}) => {
const { executor } = await createTestBed(page, { appUrl, authServerUrl });
const initOptions = executor.cordovaInitOptions();
await executor.navigateToApp();
expect(await executor.initializeAdapter(initOptions)).toBe(false);
await setupCordovaMock(page, appUrl, authServerUrl);

const loginPromise = executor.login();
await expect(loginPromise).resolves.toBeUndefined();
expect(await executor.isAuthenticated()).toBe(true);

await executor.logout(undefined, true);
expect(await executor.isAuthenticated()).toBe(false);
expect(page.context().pages().length).toBe(1);
});
});
Loading
0