mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
237 lines
7.2 KiB
TypeScript
237 lines
7.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2020 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
import { Auth, User, Persistence } from '@firebase/auth';
|
|
import { Builder, Condition, WebDriver } from 'selenium-webdriver';
|
|
import { resetEmulator } from '../../../helpers/integration/emulator_rest_helpers';
|
|
import {
|
|
getEmulatorUrl,
|
|
PROJECT_ID,
|
|
USE_EMULATOR
|
|
} from '../../../helpers/integration/settings';
|
|
import { CoreFunction } from './functions';
|
|
import { JsLoadCondition } from './js_load_condition';
|
|
import { authTestServer } from './test_server';
|
|
|
|
export const START_FUNCTION = 'startAuth';
|
|
const START_LEGACY_SDK_FUNCTION = 'startLegacySDK';
|
|
const PASSED_ARGS = '...Array.prototype.slice.call(arguments, 0, -1)';
|
|
|
|
type DriverCallResult =
|
|
| { type: 'success'; value: string /* JSON stringified */ }
|
|
| {
|
|
type: 'error';
|
|
fields: string /* JSON stringified */;
|
|
message: string;
|
|
stack: string;
|
|
};
|
|
|
|
/** Helper wrapper around the WebDriver object */
|
|
export class AuthDriver {
|
|
webDriver!: WebDriver;
|
|
|
|
async start(browser: string): Promise<void> {
|
|
await authTestServer.start();
|
|
this.webDriver = await new Builder().forBrowser(browser).build();
|
|
await this.goToTestPage();
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
authTestServer.stop();
|
|
if (process.env.WEBDRIVER_BROWSER_LOGS) {
|
|
await this.webDriver
|
|
.manage()
|
|
.logs()
|
|
.get('browser')
|
|
.then(
|
|
logs => {
|
|
for (const { level, message } of logs) {
|
|
console.log(level.name, message);
|
|
}
|
|
},
|
|
() =>
|
|
console.log(
|
|
'Failed to dump browser logs (this is normal for Firefox).'
|
|
)
|
|
);
|
|
}
|
|
await this.webDriver.quit();
|
|
}
|
|
|
|
async call<T>(fn: string, ...args: unknown[]): Promise<T> {
|
|
// When running on firefox we can't just return result immediately. For
|
|
// some reason, the binding ends up causing a cycle dependency issue during
|
|
// serialization which blows up the whole thing. It's okay though; this is
|
|
// an integration test: we don't care about the internal (hidden) values of
|
|
// these objects.
|
|
const result = await this.webDriver.executeAsyncScript<DriverCallResult>(
|
|
`
|
|
var callback = arguments[arguments.length - 1];
|
|
${fn}(${PASSED_ARGS}).then(result => {
|
|
callback({type: 'success', value: JSON.stringify(result)});
|
|
}).catch(e => {
|
|
callback({type: 'error', message: e.message, stack: e.stack, fields: JSON.stringify(e)});
|
|
});
|
|
`,
|
|
...args
|
|
);
|
|
|
|
if (result.type === 'success') {
|
|
return JSON.parse(result.value) as T;
|
|
} else {
|
|
const e = new Error(result.message);
|
|
const stack = e.stack;
|
|
Object.assign(e, JSON.parse(result.fields));
|
|
|
|
const trimmedDriverStack = result.stack.split('at eval (')[0];
|
|
e.stack = `${trimmedDriverStack}\nfrom WebDriver call ${fn}(...)\n${stack}`;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async callNoWait(fn: string, ...args: unknown[]): Promise<void> {
|
|
return this.webDriver.executeScript(`${fn}(...arguments)`, ...args);
|
|
}
|
|
|
|
async getAuthSnapshot(): Promise<Auth> {
|
|
return this.call(CoreFunction.AUTH_SNAPSHOT);
|
|
}
|
|
|
|
async getUserSnapshot(): Promise<User> {
|
|
return this.call(CoreFunction.USER_SNAPSHOT);
|
|
}
|
|
|
|
async reset(): Promise<void> {
|
|
await resetEmulator();
|
|
await this.goToTestPage();
|
|
return this.call(CoreFunction.RESET);
|
|
}
|
|
|
|
async goToTestPage(): Promise<void> {
|
|
await this.webDriver.get(authTestServer.address!);
|
|
}
|
|
|
|
async waitForAuthInit(): Promise<void> {
|
|
return this.call(CoreFunction.AWAIT_AUTH_INIT);
|
|
}
|
|
|
|
async waitForLegacyAuthInit(): Promise<void> {
|
|
return this.call(CoreFunction.AWAIT_LEGACY_AUTH_INIT);
|
|
}
|
|
|
|
async reinitOnRedirect(): Promise<void> {
|
|
// In this unique case we don't know when the page is back; check for the
|
|
// presence of the core module
|
|
await this.webDriver.wait(new JsLoadCondition(START_FUNCTION));
|
|
await this.injectConfigAndInitAuth();
|
|
await this.waitForAuthInit();
|
|
}
|
|
|
|
pause(ms: number): Promise<void> {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => resolve(), ms);
|
|
});
|
|
}
|
|
|
|
async refresh(): Promise<void> {
|
|
await this.webDriver.navigate().refresh();
|
|
await this.injectConfigAndInitAuth();
|
|
await this.waitForAuthInit();
|
|
}
|
|
|
|
private async injectConfig(): Promise<void> {
|
|
if (!USE_EMULATOR) {
|
|
throw new Error(
|
|
'Local testing against emulator requested, but ' +
|
|
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
|
|
'are missing'
|
|
);
|
|
}
|
|
|
|
await this.webDriver.executeScript(`
|
|
window.firebaseConfig = {
|
|
apiKey: 'emulator-api-key',
|
|
projectId: '${PROJECT_ID}',
|
|
authDomain: 'localhost/emulator',
|
|
};
|
|
window.emulatorUrl = '${getEmulatorUrl()}';
|
|
`);
|
|
}
|
|
|
|
async injectConfigAndInitAuth(): Promise<void> {
|
|
await this.injectConfig();
|
|
await this.call(START_FUNCTION);
|
|
}
|
|
|
|
async injectConfigAndInitLegacySDK(
|
|
persistence: Persistence['type'] = 'LOCAL'
|
|
): Promise<void> {
|
|
await this.injectConfig();
|
|
await this.call(START_LEGACY_SDK_FUNCTION, persistence);
|
|
}
|
|
|
|
async selectPopupWindow(): Promise<void> {
|
|
const currentWindowHandle = await this.webDriver.getWindowHandle();
|
|
const condition = new Condition(
|
|
'Waiting for popup to open',
|
|
async driver => {
|
|
return (await driver.getAllWindowHandles()).length > 1;
|
|
}
|
|
);
|
|
await this.webDriver.wait(condition);
|
|
const handles = await this.webDriver.getAllWindowHandles();
|
|
return this.webDriver
|
|
.switchTo()
|
|
.window(handles.find(h => h !== currentWindowHandle)!);
|
|
}
|
|
|
|
async selectMainWindow(options: { noWait?: boolean } = {}): Promise<void> {
|
|
if (!options.noWait) {
|
|
const condition = new Condition(
|
|
'Waiting for popup to close',
|
|
async driver => {
|
|
return (await driver.getAllWindowHandles()).length === 1;
|
|
}
|
|
);
|
|
await this.webDriver.wait(condition);
|
|
}
|
|
const handles = await this.webDriver.getAllWindowHandles();
|
|
return this.webDriver.switchTo().window(handles[0]);
|
|
}
|
|
|
|
async closePopup(): Promise<void> {
|
|
// This assumes the current driver is already the popup
|
|
await this.webDriver.close();
|
|
return this.selectMainWindow();
|
|
}
|
|
|
|
async closeExtraWindows(): Promise<void> {
|
|
const handles = await this.webDriver.getAllWindowHandles();
|
|
await this.webDriver.switchTo().window(handles[handles.length - 1]);
|
|
while (handles.length > 1) {
|
|
await this.webDriver.close();
|
|
handles.pop();
|
|
await this.webDriver.switchTo().window(handles[handles.length - 1]);
|
|
}
|
|
}
|
|
|
|
isCompatLayer(): boolean {
|
|
return process.env.COMPAT_LAYER === 'true';
|
|
}
|
|
}
|