Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@

# use firebase emulator for running e2e tests
NEXT_PUBLIC_FIREBASE_EMULATOR=false
FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
FIREBASE_STORAGE_EMULATOR_HOST=127.0.0.1:9199

# You already have access to basic local functionality (UI, authentication, database read access).

# openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in backend/shared/src/googleApplicationCredentials-dev.json -out secrets/googleApplicationCredentials-dev.json.enc
Expand Down
27 changes: 4 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,37 +45,18 @@ jobs:
# "common/coverage/lcov.info" \
# "web/coverage/lcov.info" \
# > coverage/lcov.info



# - name: Build app
# env:
# DATABASE_URL: ${{ secrets.DATABASE_URL }}
# run: npm run build

# Optional: Playwright E2E tests
- name: Install Playwright deps
run: |
npx playwright install --with-deps
npx playwright install chromium
# npx playwright install --with-deps
# npm install @playwright/test

- name: Run E2E tests
env:
NEXT_PUBLIC_API_URL: localhost:8088
NEXT_PUBLIC_FIREBASE_ENV: DEV
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_KEY }}
run: |
npx nyc --reporter=lcov yarn --cwd=web serve &
npx nyc --reporter=lcov yarn --cwd=backend/api dev &
npx wait-on http://localhost:3000
npx playwright test tests/e2e
SERVER_PID=$(fuser -k 3000/tcp)
echo $SERVER_PID
kill $SERVER_PID
SERVER_PID=$(fuser -k 8088/tcp)
echo $SERVER_PID
kill $SERVER_PID
chmod +x scripts/e2e.sh
./scripts/e2e.sh

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
Expand Down
2 changes: 2 additions & 0 deletions backend/api/src/helpers/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const parseCredentials = async (req: Request): Promise<Credentials> => {
try {
return {kind: 'jwt', data: await auth.verifyIdToken(payload)}
} catch (err) {
const raw = payload.split(".")[0];
console.log("JWT header:", JSON.parse(Buffer.from(raw, "base64").toString()));
// This is somewhat suspicious, so get it into the firebase console
console.error('Error verifying Firebase JWT: ', err, scheme, payload)
throw new APIError(500, 'Error validating token.')
Expand Down
17 changes: 12 additions & 5 deletions backend/shared/src/init-admin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import * as admin from 'firebase-admin'


import {getServiceAccountCredentials} from "shared/firebase-utils";
import {IS_LOCAL} from "common/hosting/constants";
import {IS_FIREBASE_EMULATOR} from "common/envs/constants";

// Locally initialize Firebase Admin.
export const initAdmin = () => {

if (IS_LOCAL && IS_FIREBASE_EMULATOR) {
console.log("Using Firebase Emulator Suite.")
return admin.initializeApp({
projectId: "compass-57c3c",
storageBucket: "compass-130ba-public",
})
}

if (IS_LOCAL) {
try {
const serviceAccount = getServiceAccountCredentials()
// console.debug(serviceAccount)

if (!serviceAccount.project_id) {
console.debug(`GOOGLE_APPLICATION_CREDENTIALS not set, skipping admin firebase init.`)
return
Expand All @@ -27,4 +34,4 @@ export const initAdmin = () => {

console.debug(`Initializing connection to default Firebase...`)
return admin.initializeApp()
}
}
3 changes: 3 additions & 0 deletions common/src/envs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ console.debug(`Running in ${HOSTING_ENV} (${ENV})`,);
// throw new MissingKeyError('firebaseConfig.apiKey')
// }

export const IS_FIREBASE_EMULATOR = process.env.NEXT_PUBLIC_FIREBASE_EMULATOR === "true"
if (IS_FIREBASE_EMULATOR) console.log("Using Firebase emulator.")

export const LOCAL_WEB_DOMAIN = `localhost:3000`
export const LOCAL_BACKEND_DOMAIN = `${IS_WEBVIEW_DEV_PHONE ? '192.168.1.3' : IS_LOCAL_ANDROID ? '10.0.2.2' : 'localhost'}:8088`

Expand Down
11 changes: 10 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,14 @@
"bucket": "compass-130ba-private",
"rules": "private-storage.rules"
}
]
],
"emulators": {
"auth": {
"port": 9099
},
"ui": {
"enabled": true,
"port": 4000
}
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"playwright:ui": "playwright test --ui",
"playwright:debug": "playwright test --debug",
"playwright:report": "npx playwright show-report tests/reports/playwright-report",
"postinstall": "./scripts/post_install.sh"
"postinstall": "./scripts/post_install.sh",
"emulate": "firebase emulators:start --only auth --project compass-57c3c"
},
"dependencies": {
"@capacitor/app": "7.1.0",
Expand Down Expand Up @@ -65,6 +66,7 @@
"eslint": "8.57.0",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-unused-imports": "4.1.4",
"firebase-tools": "^14.26.0",
"jest": "29.3.1",
"nodemon": "2.0.20",
"prettier": "3.6.2",
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defineConfig, devices } from '@playwright/test';
import path from 'path';

export default defineConfig({
testDir: './tests/e2e',
Expand Down Expand Up @@ -27,5 +26,6 @@ export default defineConfig({
// use: { ...devices['Desktop Safari'] },
// },
],
timeout: 60000,

});
42 changes: 31 additions & 11 deletions scripts/e2e.sh
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
#!/bin/bash

# set -e
set -euo pipefail

# Function to clean up background processes
cleanup() {
echo "Stopping background processes..."
for pid in "${PIDS[@]:-}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" || true
wait "$pid" 2>/dev/null || true
echo "Killed PID $pid"
fi
done
}

# Trap EXIT, INT, TERM to run cleanup automatically
trap cleanup EXIT INT TERM

cd "$(dirname "$0")"/..

npx playwright install chromium

export NEXT_PUBLIC_API_URL=localhost:8088
export NEXT_PUBLIC_FIREBASE_ENV=DEV
export NEXT_PUBLIC_FIREBASE_EMULATOR=true
export FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099
export FIREBASE_STORAGE_EMULATOR_HOST=127.0.0.1:9199
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may also have been one key missing line.


# Start servers in background and store their PIDs
PIDS=()
npx nyc --reporter=lcov yarn --cwd=web serve & PIDS+=($!)
npx nyc --reporter=lcov yarn --cwd=backend/api dev & PIDS+=($!)
yarn emulate & PIDS+=($!)

npx nyc --reporter=lcov yarn --cwd=web serve &
npx nyc --reporter=lcov yarn --cwd=backend/api dev &
npx wait-on http://localhost:3000
npx playwright test tests/e2e --headed
SERVER_PID=$(fuser -k 3000/tcp)
echo $SERVER_PID
kill $SERVER_PID

SERVER_PID=$(fuser -k 8088/tcp)
echo $SERVER_PID
kill $SERVER_PID

npx tsx scripts/setup-auth.ts

npx playwright test tests/e2e

exit ${TEST_FAILED:-0}
16 changes: 16 additions & 0 deletions scripts/setup-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axios from 'axios';
import { config } from '../tests/e2e/web/SPEC_CONFIG.js';

async function createAuth() {
const base = 'http://localhost:9099/identitytoolkit.googleapis.com/v1';

await axios.post(`${base}/accounts:signUp?key=fake-api-key`, {
email: config.USERS.DEV_1.EMAIL,
password: config.USERS.DEV_1.PASSWORD,
returnSecureToken: true
});

console.log('Auth created', config.USERS.DEV_1.EMAIL)

}
createAuth();
23 changes: 23 additions & 0 deletions tests/e2e/web/SPEC_CONFIG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const config = {
BASE_URL: 'http://localhost:3000',

USERS: {
DEV_1: {
EMAIL: '[email protected]',
PASSWORD: 'dev_1Password',
},
DEV_2: {
EMAIL: '[email protected]',
PASSWORD: 'dev_2Password',
},
SPEC: {
EMAIL: '[email protected]',
PASSWORD: 'compassConnections1!',
},
SPEC_GOOGLE: {
EMAIL: '[email protected]',
//unsure if gmail password should be public
PASSWORD: '',
}
},
};
5 changes: 0 additions & 5 deletions tests/e2e/web/TESTING_CONFIG.ts

This file was deleted.

42 changes: 42 additions & 0 deletions tests/e2e/web/fixtures/deleteUserFixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { test as base } from '@playwright/test';
import axios from 'axios';
import { config } from '../SPEC_CONFIG';

const baseUrl = 'http://localhost:9099/identitytoolkit.googleapis.com/v1';

async function deleteUser(email: string, password: string) {
try {
const login = await axios.post(
`${baseUrl}/accounts:signInWithPassword?key=fake-api-key`,
{
email,
password,
returnSecureToken: true
}
);

await axios.post(
`${baseUrl}/accounts:delete?key=fake-api-key`,
{ idToken: login.data.idToken }
);
} catch (err: any) {
console.log(err);
}
}

type CleanupFixtures = {
cleanupUsers: void;
};

export const test = base.extend<CleanupFixtures>({
cleanupUsers: [
async ({}, use) => {
// Run all tests first
await use();

//then delete users
await deleteUser(config.USERS.SPEC.EMAIL, config.USERS.SPEC.PASSWORD);
},
{ auto: true },
],
});
16 changes: 7 additions & 9 deletions tests/e2e/web/fixtures/signInFixture.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { test as base, Page, expect } from '@playwright/test';
import { SignInPage } from '../pages/signInPage';
import { config } from '../TESTING_CONFIG';
import { AuthPage } from '../pages/AuthPage';
import { config } from '../SPEC_CONFIG';

export const test = base.extend<{
authenticatedPage: Page;
}>({
authenticatedPage: async ({ page }, use) => {
const signInPage = new SignInPage(page);
const authPage = new AuthPage(page);

await page.goto('/signin');
await signInPage.fillEmailField(config.DEFAULT_LOGIN);
await signInPage.fillPasswprdField(config.DEFAULT_PASSWORD);
await signInPage.clickSignInWithEmailButton();
await authPage.fillEmailField(config.USERS.DEV_1.EMAIL);
await authPage.fillPasswordField(config.USERS.DEV_1.PASSWORD);
await authPage.clickSignInWithEmailButton();

await page.waitForLoadState('networkidle');

await page.waitForURL('/');
await page.waitForURL(/^(?!.*signin).*$/);

expect(page.url()).not.toContain('/signin')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
import { expect, Locator, Page } from '@playwright/test';

//sets up of all the functions that signin tests will use.
export class SignInPage{
export class AuthPage{
private readonly signInLink: Locator;
private readonly signUpButton: Locator;
private readonly emailField: Locator;
private readonly passwordField: Locator;
private readonly signInWithEmailButton: Locator;
private readonly signInWithGoogleButton: Locator;
private readonly signUpWithEmailButton: Locator;

constructor(public readonly page: Page) {
this.signInLink=page.getByRole('link', { name: 'Sign in' });
this.signUpButton=page.getByRole('button', {name: 'Sign up'});
this.emailField=page.getByLabel('Email');
this.passwordField=page.getByLabel('Password');
this.signInWithEmailButton=page.getByRole('button',{name: 'Sign in With Email'});
this.signInWithGoogleButton=page.getByRole('button',{name: 'Google'});
this.signInWithEmailButton=page.getByRole('button', {name: 'Sign in with Email'});
this.signInWithGoogleButton=page.getByRole('button', {name: 'Google'});
this.signUpWithEmailButton=page.getByRole('button', {name: 'Sign up with Email'});
}

async clickSignInText() {
async clickSignInLink() {
await this.signInLink.click();
}

async clickSignUpButton() {
await this.signUpButton.click();
}

async clickSignInWithEmailButton() {
await this.signInWithEmailButton.click();
}

async clickSignInWithEGoogleButton() {
async clickSignInWithGoogleButton() {
await this.signInWithGoogleButton.click();
}

async clickSignUpWithEmailButton() {
await this.signUpWithEmailButton.click();
}

async fillEmailField(email: string) {
await expect(this.emailField).toBeVisible();
await this.emailField.fill(email);
}

async fillPasswprdField(password: string) {
async fillPasswordField(password: string) {
await expect(this.passwordField).toBeVisible();
await this.passwordField.fill(password);
}
Expand Down
Loading
Loading