Skip to content

Commit b6caa3f

Browse files
sam-gcyuchenshi
andauthored
[Auth] Add some initial WebDriver tests against the emulator (#4580)
* Add webdriver tests as well as updated readme * Formatting * Fix markup * Move location of integration command in package.json * PR feedback, use emulators:exec instead * Formatting * Clean up the test infra a bit * Formatting * Update packages-exp/auth-exp/README.md Co-authored-by: Yuchen Shi <[email protected]> * PR feedback Co-authored-by: Yuchen Shi <[email protected]>
1 parent 4f8cfcf commit b6caa3f

File tree

17 files changed

+557
-55
lines changed

17 files changed

+557
-55
lines changed

packages-exp/auth-exp/README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,55 @@
22

33
This is the Firebase Authentication component of the Firebase JS SDK.
44

5-
**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.**
5+
**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.**
6+
7+
## Testing
8+
9+
The modular Auth SDK has both unit tests and integration tests, along with a
10+
host of npm scripts to run these tests. The most important commands are:
11+
12+
| Command | Description |
13+
| ------- | ----------- |
14+
| `yarn test` | This will run lint, unit tests, and integration tests against the live environment|
15+
| `yarn test:<platform>` | Runs all browser tests, unit and integration |
16+
| `yarn test:<platform>:unit` | Runs only \<platform> unit tests |
17+
| `yarn test:<platform>:unit:debug` | Runs \<platform> unit tests, auto-watching for file system changes |
18+
| `yarn test:<platform>:integration` | Runs only integration tests against the live environment |
19+
| `yarn test:<platform>:integration:local` | Runs all headless \<platform> integration tests against the emulator (more below) |
20+
21+
Where \<platform> is "browser" or "node". There are also cordova tests, but they
22+
are not broken into such granular details. Check out `package.json` for more.
23+
24+
### Integration testing with the emulator
25+
26+
To test against the emulator, set up the Auth emulator
27+
([instructions](https://firebase.google.com/docs/emulator-suite/connect_and_prototype)).
28+
The easiest way to run these tests is to use the `firebase emulators:exec`
29+
command
30+
([documentation](https://firebase.google.com/docs/emulator-suite/install_and_configure#startup)).
31+
You can also manually start the emulator separately, and then point the tests
32+
to it by setting the `GCLOUD_PROJECT` and `FIREBASE_AUTH_EMULATOR_HOST`
33+
environmental variables. In addition to the commands listed above, the below
34+
commands also run various tests:
35+
36+
* `yarn test:integration:local` — Executes Node and browser emulator
37+
integration tests, as well as the Selenium WebDriver tests
38+
39+
* `yarn test:webdriver` — Executes only the Selenium WebDriver
40+
integration tests
41+
42+
For example, to run all integration and WebDriver tests against the emulator,
43+
you would simply execute the following command:
44+
45+
```sh
46+
firebase emulators:exec --project foo-bar --only auth "yarn test:integration:local"
47+
```
48+
49+
### Selenium Webdriver tests
50+
51+
These tests assume that you have both Firefox and Chrome installed on your
52+
computer and in your `$PATH`. The tests will error out if this is not the case.
53+
The WebDriver tests talk to the emulator, but unlike the headless integration
54+
tests, these run in a browser robot environment; the assertions themselves run
55+
in Node. When you run these tests a small Express server will be started to
56+
serve the static files the browser robot uses.

packages-exp/auth-exp/karma.conf.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ function getTestFiles(argv) {
3737
return ['src/**/*.test.ts', 'test/helpers/**/*.test.ts'];
3838
} else if (argv.integration) {
3939
return argv.local
40-
? ['test/integration/**/*.test.ts']
41-
: ['test/integration/**/*!(local).test.ts'];
40+
? ['test/integration/flows/*.test.ts']
41+
: ['test/integration/flows/*!(local).test.ts'];
4242
} else if (argv.cordova) {
4343
return ['src/platform_cordova/**/*.test.ts'];
4444
} else {
@@ -57,13 +57,22 @@ function getClientConfig(argv) {
5757
return {};
5858
}
5959

60+
if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) {
61+
console.error(
62+
'Local testing against emulator requested, but ' +
63+
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
64+
'are missing'
65+
);
66+
process.exit(1);
67+
}
68+
6069
return {
6170
authAppConfig: {
6271
apiKey: 'local-api-key',
63-
projectId: 'test-emulator',
72+
projectId: process.env.GCLOUD_PROJECT,
6473
authDomain: 'local-auth-domain'
6574
},
66-
authEmulatorPort: '9099'
75+
authEmulatorHost: process.env.FIREBASE_AUTH_EMULATOR_HOST
6776
};
6877
}
6978

packages-exp/auth-exp/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"test": "run-p lint test:all",
2626
"test:all": "run-p test:browser test:node",
2727
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all",
28+
"test:integration:local": "run-s test:node:integration:local test:browser:integration:local test:webdriver",
2829
"test:browser": "karma start --single-run",
2930
"test:browser:unit": "karma start --single-run --unit",
3031
"test:browser:integration": "karma start --single-run --integration",
@@ -37,6 +38,7 @@
3738
"test:node:unit": "node ./scripts/run-node-tests.js",
3839
"test:node:integration": "node ./scripts/run-node-tests.js --integration",
3940
"test:node:integration:local": "node ./scripts/run-node-tests.js --integration --local",
41+
"test:webdriver": "rollup -c test/integration/webdriver/static/rollup.config.js && node ./scripts/run-node-tests.js --webdriver",
4042
"api-report": "api-extractor run --local --verbose",
4143
"predoc": "node ../../scripts/exp/remove-exp.js temp",
4244
"doc": "api-documenter markdown --input temp --output docs",
@@ -47,17 +49,18 @@
4749
"@firebase/app-exp": "0.x"
4850
},
4951
"dependencies": {
52+
"@firebase/component": "0.2.0",
5053
"@firebase/logger": "0.2.6",
5154
"@firebase/util": "0.3.4",
52-
"@firebase/component": "0.2.0",
5355
"node-fetch": "2.6.1",
56+
"selenium-webdriver": "4.0.0-beta.1",
5457
"tslib": "^2.0.0"
5558
},
5659
"license": "Apache-2.0",
5760
"devDependencies": {
5861
"@firebase/app-exp": "0.0.900",
59-
"rollup": "2.35.1",
6062
"@rollup/plugin-json": "4.1.0",
63+
"rollup": "2.35.1",
6164
"rollup-plugin-sourcemaps": "0.6.3",
6265
"rollup-plugin-typescript2": "0.29.0",
6366
"@rollup/plugin-strip": "2.0.0",

packages-exp/auth-exp/scripts/run-node-tests.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ var child_process_promise_1 = require('child-process-promise');
3030
var yargs = require('yargs');
3131
var argv = yargs.options({
3232
local: { type: 'boolean' },
33-
integration: { type: 'boolean' }
33+
integration: { type: 'boolean' },
34+
webdriver: { type: 'boolean' }
3435
}).argv;
3536
var nyc = path_1.resolve(__dirname, '../../../node_modules/.bin/nyc');
3637
var mocha = path_1.resolve(__dirname, '../../../node_modules/.bin/mocha');
@@ -45,20 +46,33 @@ if (argv.integration) {
4546
if (argv.local) {
4647
testConfig.push('test/integration/flows/*.local.test.ts');
4748
}
49+
} else if (argv.webdriver) {
50+
testConfig = ['test/integration/webdriver/**.test.ts', '--delay'];
4851
}
4952
var args = __spreadArrays(['--reporter', 'lcovonly', mocha], testConfig, [
5053
'--config',
5154
'../../config/mocharc.node.js'
5255
]);
5356
if (argv.local) {
54-
process.env.AUTH_EMULATOR_PORT = '9099';
55-
process.env.AUTH_EMULATOR_PROJECT_ID = 'test-emulator';
57+
if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) {
58+
console.error(
59+
'Local testing against emulator requested, but ' +
60+
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
61+
'are missing'
62+
);
63+
process.exit(1);
64+
}
5665
}
5766
args = args.concat(argv._);
58-
var childProcess = child_process_promise_1.spawn(nyc, args, {
67+
var spawned = child_process_promise_1.spawn(nyc, args, {
5968
stdio: 'inherit',
6069
cwd: process.cwd()
61-
}).childProcess;
70+
});
71+
var childProcess = spawned.childProcess;
72+
spawned['catch'](function () {
73+
childProcess.kill();
74+
process.exit(1);
75+
});
6276
process.once('exit', function () {
6377
return childProcess.kill();
6478
});

packages-exp/auth-exp/scripts/run-node-tests.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const argv = yargs.options({
2626
},
2727
integration: {
2828
type: 'boolean'
29+
},
30+
webdriver: {
31+
type: 'boolean'
2932
}
3033
}).argv;
3134

@@ -45,6 +48,8 @@ if (argv.integration) {
4548
if (argv.local) {
4649
testConfig.push('test/integration/flows/*.local.test.ts');
4750
}
51+
} else if (argv.webdriver) {
52+
testConfig = ['test/integration/webdriver/**.test.ts', '--delay'];
4853
}
4954

5055
let args = [
@@ -56,17 +61,30 @@ let args = [
5661
'../../config/mocharc.node.js'
5762
];
5863

64+
// Make sure that the environment variables are present for local test
5965
if (argv.local) {
60-
process.env.AUTH_EMULATOR_PORT = '9099';
61-
process.env.AUTH_EMULATOR_PROJECT_ID = 'test-emulator';
66+
if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) {
67+
console.error(
68+
'Local testing against emulator requested, but ' +
69+
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
70+
'are missing'
71+
);
72+
process.exit(1);
73+
}
6274
}
6375

6476
args = args.concat(argv._ as string[]);
6577

66-
const childProcess = spawn(nyc, args, {
78+
const spawned = spawn(nyc, args, {
6779
stdio: 'inherit',
6880
cwd: process.cwd()
69-
}).childProcess;
81+
});
82+
83+
const childProcess = spawned.childProcess;
84+
spawned.catch(() => {
85+
childProcess.kill();
86+
process.exit(1);
87+
});
7088

7189
process.once('exit', () => childProcess.kill());
7290
process.once('SIGINT', () => childProcess.kill('SIGINT'));

packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
* limitations under the License.
1616
*/
1717

18-
// eslint-disable-next-line import/no-extraneous-dependencies
19-
import { Auth } from '@firebase/auth-exp';
20-
import { getApps } from '@firebase/app-exp';
2118
import { FetchProvider } from '../../../src/core/util/fetch_provider';
2219
import * as fetchImpl from 'node-fetch';
20+
import { getAppConfig, getEmulatorUrl } from './settings';
2321

2422
if (typeof document !== 'undefined') {
2523
FetchProvider.initialize(fetch);
@@ -52,11 +50,10 @@ interface OobCodesResponse {
5250
oobCodes: OobCodeSession[];
5351
}
5452

55-
export async function getPhoneVerificationCodes(
56-
auth: Auth
57-
): Promise<Record<string, VerificationSession>> {
58-
assertEmulator(auth);
59-
const url = getEmulatorUrl(auth, 'verificationCodes');
53+
export async function getPhoneVerificationCodes(): Promise<
54+
Record<string, VerificationSession>
55+
> {
56+
const url = buildEmulatorUrlForPath('verificationCodes');
6057
const response: VerificationCodesResponse = await (
6158
await FetchProvider.fetch()(url)
6259
).json();
@@ -67,27 +64,21 @@ export async function getPhoneVerificationCodes(
6764
}, {} as Record<string, VerificationSession>);
6865
}
6966

70-
export async function getOobCodes(auth: Auth): Promise<OobCodeSession[]> {
71-
assertEmulator(auth);
72-
const url = getEmulatorUrl(auth, 'oobCodes');
67+
export async function getOobCodes(): Promise<OobCodeSession[]> {
68+
const url = buildEmulatorUrlForPath('oobCodes');
7369
const response: OobCodesResponse = await (
7470
await FetchProvider.fetch()(url)
7571
).json();
7672
return response.oobCodes;
7773
}
7874

79-
function getEmulatorUrl(auth: Auth, endpoint: string): string {
80-
const { host, port, protocol } = auth.emulatorConfig!;
81-
const projectId = getProjectId(auth);
82-
return `${protocol}://${host}:${port}/emulator/v1/projects/${projectId}/${endpoint}`;
75+
export async function resetEmulator(): Promise<void> {
76+
const url = buildEmulatorUrlForPath('accounts');
77+
await FetchProvider.fetch()(url, { method: 'DELETE' });
8378
}
8479

85-
function getProjectId(auth: Auth): string {
86-
return getApps().find(app => app.name === auth.name)!.options.projectId!;
87-
}
88-
89-
function assertEmulator(auth: Auth): void {
90-
if (!auth.emulatorConfig) {
91-
throw new Error("Can't fetch OOB codes against prod API");
92-
}
80+
function buildEmulatorUrlForPath(endpoint: string): string {
81+
const emulatorBaseUrl = getEmulatorUrl();
82+
const projectId = getAppConfig().projectId;
83+
return `${emulatorBaseUrl}/emulator/v1/projects/${projectId}/${endpoint}`;
9384
}

packages-exp/auth-exp/test/helpers/integration/helpers.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Auth, User } from '../../../src/model/public_types';
2222
import { getAuth, useAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env.
2323
import { _generateEventId } from '../../../src/core/util/event_id';
2424
import { getAppConfig, getEmulatorUrl } from './settings';
25+
import { resetEmulator } from './emulator_rest_helpers';
2526

2627
interface IntegrationTestAuth extends Auth {
2728
cleanUp(): Promise<void>;
@@ -55,12 +56,17 @@ export function getTestInstance(requireEmulator = false): Auth {
5556
});
5657

5758
auth.cleanUp = async () => {
58-
// Clear out any new users that were created in the course of the test
59-
for (const user of createdUsers) {
60-
try {
61-
await user.delete();
62-
} catch {
63-
// Best effort. Maybe the test already deleted the user ¯\_(ツ)_/¯
59+
// If we're in an emulated environment, the emulator will clean up for us
60+
if (emulatorUrl) {
61+
await resetEmulator();
62+
} else {
63+
// Clear out any new users that were created in the course of the test
64+
for (const user of createdUsers) {
65+
try {
66+
await user.delete();
67+
} catch {
68+
// Best effort. Maybe the test already deleted the user ¯\_(ツ)_/¯
69+
}
6470
}
6571
}
6672

packages-exp/auth-exp/test/helpers/integration/settings.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ declare const __karma__: any;
2424
// eslint-disable-next-line @typescript-eslint/no-require-imports
2525
const PROJECT_CONFIG = require('../../../../../config/project.json');
2626

27-
const EMULATOR_PORT = process.env.AUTH_EMULATOR_PORT;
28-
const EMULATOR_PROJECT_ID = process.env.AUTH_EMULATOR_PROJECT_ID;
27+
const EMULATOR_HOST = process.env.FIREBASE_AUTH_EMULATOR_HOST;
28+
const EMULATOR_PROJECT_ID = process.env.GCLOUD_PROJECT;
2929

30-
export const USE_EMULATOR = !!EMULATOR_PORT;
30+
export const USE_EMULATOR = !!EMULATOR_HOST;
3131

3232
export const PROJECT_ID = USE_EMULATOR
3333
? EMULATOR_PROJECT_ID
@@ -52,11 +52,10 @@ export function getAppConfig(): FirebaseOptions {
5252

5353
export function getEmulatorUrl(): string | null {
5454
// Check karma first, then fallback on node process
55-
const emulatorPort: string | null =
56-
getKarma()?.config?.authEmulatorPort ||
57-
(USE_EMULATOR ? EMULATOR_PORT : null);
58-
59-
return emulatorPort ? `http://localhost:${emulatorPort}` : null;
55+
const host =
56+
getKarma()?.config?.authEmulatorHost ||
57+
(USE_EMULATOR ? EMULATOR_HOST : null);
58+
return host ? `http://${host}` : null;
6059
}
6160

6261
// eslint-disable-next-line @typescript-eslint/no-explicit-any

packages-exp/auth-exp/test/integration/flows/oob.local.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('Integration test: oob codes', () => {
7777
});
7878

7979
async function code(toEmail: string): Promise<OobCodeSession> {
80-
const codes = await getOobCodes(auth);
80+
const codes = await getOobCodes();
8181
return codes.reverse().find(({ email }) => email === toEmail)!;
8282
}
8383

packages-exp/auth-exp/test/integration/flows/phone.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('Integration test: phone auth', () => {
8888
fallback: string
8989
): Promise<string> {
9090
if (auth.emulatorConfig) {
91-
const codes = await getPhoneVerificationCodes(auth);
91+
const codes = await getPhoneVerificationCodes();
9292
const vid = typeof crOrId === 'string' ? crOrId : crOrId.verificationId;
9393
return codes[vid].code;
9494
}

0 commit comments

Comments
 (0)