Skip to content

Commit

Permalink
Merge pull request #1142 from jetstreamapp/feat/web-extension-auth-pl…
Browse files Browse the repository at this point in the history
…aywright

Add web-extension tests
  • Loading branch information
paustint authored Jan 21, 2025
2 parents 27bac1f + 8566752 commit e1026fb
Show file tree
Hide file tree
Showing 46 changed files with 513 additions and 133 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ package-lock.json

.nx/cache
.nx/workspace-data
**/playwright/.auth/user.json
**/playwright/**/*
!**/playwright/.gitkeep

# Ignore data directory in scripts
/scripts/**/data/**/*
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/db/user.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const checkUserEntitlement = ({
userId: string;
entitlement: keyof Omit<Entitlement, 'id' | 'userId' | 'createdAt' | 'updatedAt'>;
}): Promise<boolean> => {
return prisma.entitlement.count({ where: { id: userId, [entitlement]: true } }).then((result) => result > 0);
return prisma.entitlement.count({ where: { userId, [entitlement]: true } }).then((result) => result > 0);
};

export async function updateUser(
Expand Down
10 changes: 9 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,16 @@ try {
lastLoggedIn: new Date(),
preferences: { create: { skipFrontdoorLogin: false } },
authFactors: { create: { type: '2fa-email', enabled: false } },
entitlements: { create: { chromeExtension: true, recordSync: true, googleDrive: true } },
},
update: {
entitlements: {
upsert: {
create: { chromeExtension: true, recordSync: true, googleDrive: true },
update: { chromeExtension: true, recordSync: true, googleDrive: true },
},
},
},
update: {},
where: { id: user.id },
});
logger.info('Example user created');
Expand Down
1 change: 1 addition & 0 deletions apps/jetstream-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"sourceRoot": "apps/jetstream-e2e/src",
"projectType": "application",
"implicitDependencies": ["jetstream"],
"tags": ["scope:e2e"],
"targets": {
"e2e": {
"executor": "@nx/playwright:playwright",
Expand Down
18 changes: 10 additions & 8 deletions apps/jetstream-e2e/src/fixtures/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import * as dotenv from 'dotenv';

import {
ApiRequestUtils,
AuthenticationPage,
LoadSingleObjectPage,
LoadWithoutFilePage,
OrganizationsPage,
PlatformEventPage,
PlaywrightPage,
QueryPage,
} from '@jetstream/test/e2e-utils';
import { test as base } from '@playwright/test';
import { z } from 'zod';
import { AuthenticationPage } from '../pageObjectModels/AuthenticationPage.model';
import { LoadSingleObjectPage } from '../pageObjectModels/LoadSingleObjectPage.model';
import { LoadWithoutFilePage } from '../pageObjectModels/LoadWithoutFilePage.model';
import { OrganizationsPage } from '../pageObjectModels/OrganizationsPage';
import { PlatformEventPage } from '../pageObjectModels/PlatformEventPage.model';
import { PlaywrightPage } from '../pageObjectModels/PlaywrightPage.model';
import { QueryPage } from '../pageObjectModels/QueryPage.model';
import { ApiRequestUtils } from './ApiRequestUtils';

globalThis.__IS_CHROME_EXTENSION__ = false;

Expand Down
2 changes: 1 addition & 1 deletion apps/jetstream-e2e/src/setup/global.setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable playwright/no-standalone-expect */
import { ENV } from '@jetstream/api-config';
import { expect, test as setup } from '@playwright/test';
import { join } from 'path';
Expand Down Expand Up @@ -27,7 +28,6 @@ setup('login and ensure org exists', async ({ page, request }) => {

await page.waitForURL(`${baseApiURL}/app`);

// eslint-disable-next-line playwright/no-standalone-expect
await expect(page.getByRole('button', { name: 'Avatar' })).toBeVisible();

await page.evaluate(async () => {
Expand Down
10 changes: 5 additions & 5 deletions apps/jetstream-e2e/src/tests/authentication/login1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test.describe('Login 1', () => {

await test.step('Login and verify email and logout', async () => {
await authenticationPage.loginAndVerifyEmail(email, password);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
await playwrightPage.goToProfile();
await expect(page.getByText(name)).toBeVisible();
await expect(page.getByText(email)).toBeVisible();
Expand All @@ -40,7 +40,7 @@ test.describe('Login 1', () => {
await test.step('Login without MFA', async () => {
await authenticationPage.fillOutLoginForm(email, password);
await page.waitForURL(`**/app`);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
});
});

Expand All @@ -54,23 +54,23 @@ test.describe('Login 1', () => {

await test.step('Login with remembered device', async () => {
await authenticationPage.loginAndVerifyEmail(email, password, true);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
await playwrightPage.logout();
await expect(page.getByTestId('home-hero-container')).toBeVisible();
});

await test.step('Should not need 2fa since device is remembered', async () => {
await authenticationPage.fillOutLoginForm(email, password);
await page.waitForURL(`**/app`);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
await playwrightPage.logout();
await expect(page.getByTestId('home-hero-container')).toBeVisible();
});

await test.step('Email address should be case-insensitive', async () => {
await authenticationPage.fillOutLoginForm(email.toUpperCase(), password);
await page.waitForURL(`**/app`);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
});
});
});
10 changes: 5 additions & 5 deletions apps/jetstream-e2e/src/tests/authentication/login2.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { prisma } from '@jetstream/api-config';
import { getPasswordResetToken } from '@jetstream/test/e2e-utils';
import { expect, test } from '../../fixtures/fixtures';
import { getPasswordResetToken } from '../../utils/database-validation.utils';

test.beforeEach(async ({ page }) => {
await page.goto('/');
Expand Down Expand Up @@ -74,7 +74,7 @@ test.describe('Login 2', () => {
await playwrightPage.logout();

await authenticationPage.loginAndVerifyTotp(email, password, secret);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');

await playwrightPage.goToProfile();

Expand All @@ -95,7 +95,7 @@ test.describe('Login 2', () => {
// Should not need 2fa since device is remembered
await authenticationPage.fillOutLoginForm(email, password);
await page.waitForURL(`**/app`);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');

// re-enable TOTP to make sure that works
await playwrightPage.goToProfile();
Expand All @@ -107,7 +107,7 @@ test.describe('Login 2', () => {

// Ensure 2fa is reactivated on logout and login
await authenticationPage.loginAndVerifyTotp(email, password, secret);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');

// Delete TOTP and ensure that logout/login works successfully
await playwrightPage.goToProfile();
Expand All @@ -121,6 +121,6 @@ test.describe('Login 2', () => {

await authenticationPage.fillOutLoginForm(email, password);
await page.waitForURL(`**/app`);
await expect(page.url()).toContain('/app');
expect(page.url()).toContain('/app');
});
});
4 changes: 2 additions & 2 deletions apps/jetstream-e2e/src/tests/authentication/login3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ test.describe('Login 3', () => {

await test.step('Ensure authenticated API fails prior to email verification', async () => {
const response = await apiRequestUtils.makeRequestRaw('GET', '/api/me', { Accept: 'application/json' });
await expect(response.status()).toBe(401);
expect(response.status()).toBe(401);
});

await test.step('Verify email and ensure we can make an authenticated API request', async () => {
await authenticationPage.verifyEmail(email);
const response = await apiRequestUtils.makeRequestRaw('GET', '/api/me', { Accept: 'application/json' });
await expect(response.status()).toBe(200);
expect(response.status()).toBe(200);
});
});

Expand Down
1 change: 1 addition & 0 deletions apps/jetstream-e2e/src/tests/load/load.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test.beforeEach(async ({ page }) => {
test.describe.configure({ mode: 'parallel' });

test.describe('LOAD RECORDS', () => {
// eslint-disable-next-line playwright/expect-expect
test('Should upload file', async ({ loadSingleObjectPage, page }) => {
const csvFile = join(__dirname, `../../assets/records-Product2.csv`);

Expand Down
1 change: 1 addition & 0 deletions apps/jetstream-e2e/src/tests/query/query-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test.describe.configure({ mode: 'parallel' });

test.describe('QUERY BUILDER', () => {
// TODO: add test for drilling in to related query filters
// eslint-disable-next-line playwright/expect-expect
test('should work with filters', async ({ queryPage }) => {
await queryPage.goto();
await queryPage.selectObject('Account');
Expand Down
22 changes: 11 additions & 11 deletions apps/jetstream-e2e/src/tests/security/security.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ test.describe('Security Checks', () => {

await test.step('Try to update or delete org from different user', async () => {
const orgsResponse = await apiRequestUtils.makeRequestRaw('GET', `/api/orgs`);
await expect(orgsResponse.ok()).toEqual(true);
expect(orgsResponse.ok()).toEqual(true);

const updateSalesforceOrgResponse = await apiRequestUtils.makeRequestRaw('PATCH', `/api/orgs/${salesforceOrg.uniqueId}`, {
jetstreamOrganizationId: salesforceOrg.jetstreamOrganizationId,
Expand Down Expand Up @@ -102,12 +102,12 @@ test.describe('Security Checks', () => {
createdAt: salesforceOrg.createdAt,
updatedAt: salesforceOrg.updatedAt,
});
await expect(updateSalesforceOrgResponse.ok()).toBeFalsy();
await expect(updateSalesforceOrgResponse.status()).toBe(404);
expect(updateSalesforceOrgResponse.ok()).toBeFalsy();
expect(updateSalesforceOrgResponse.status()).toBe(404);

const deleteSalesforceOrgResponse = await apiRequestUtils.makeRequestRaw('DELETE', `/api/orgs/${salesforceOrg.uniqueId}`);
await expect(deleteSalesforceOrgResponse.ok()).toBeFalsy();
await expect(deleteSalesforceOrgResponse.status()).toBe(404);
expect(deleteSalesforceOrgResponse.ok()).toBeFalsy();
expect(deleteSalesforceOrgResponse.status()).toBe(404);
});

await test.step('Try to use an org from a different user for a query API request', async () => {
Expand All @@ -119,8 +119,8 @@ test.describe('Security Checks', () => {
'X-SFDC-ID': salesforceOrg.uniqueId,
}
);
await expect(useOrgFromDifferentUserResponse.ok()).toBeFalsy();
await expect(useOrgFromDifferentUserResponse.status()).toBe(404);
expect(useOrgFromDifferentUserResponse.ok()).toBeFalsy();
expect(useOrgFromDifferentUserResponse.status()).toBe(404);
});

await test.step('Try to update and delete a Jetstream organization from a different user', async () => {
Expand All @@ -132,12 +132,12 @@ test.describe('Security Checks', () => {
createdAt: jetstreamOrg.createdAt,
updatedAt: jetstreamOrg.updatedAt,
});
await expect(updateJetstreamResponse.ok()).toBeFalsy();
await expect(updateJetstreamResponse.status()).toBe(404);
expect(updateJetstreamResponse.ok()).toBeFalsy();
expect(updateJetstreamResponse.status()).toBe(404);

const deleteJetstreamResponse = await apiRequestUtils.makeRequestRaw('DELETE', `/api/orgs/${jetstreamOrg.id}`);
await expect(deleteJetstreamResponse.ok()).toBeFalsy();
await expect(deleteJetstreamResponse.status()).toBe(404);
expect(deleteJetstreamResponse.ok()).toBeFalsy();
expect(deleteJetstreamResponse.status()).toBe(404);
});
});
});
5 changes: 3 additions & 2 deletions apps/jetstream-web-extension-e2e/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ module.exports = [
...compat.extends('plugin:playwright/recommended'),
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
rules: {
'@typescript-eslint/no-unused-vars': 'off',
},
},
{
files: ['**/*.ts', '**/*.tsx'],
Expand Down
84 changes: 37 additions & 47 deletions apps/jetstream-web-extension-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,59 @@
import { defineConfig, devices } from '@playwright/test';
import { nxE2EPreset } from '@nx/playwright/preset';
import { defineConfig, devices } from '@playwright/test';
import * as dotenv from 'dotenv';

dotenv.config();

import { workspaceRoot } from '@nx/devkit';
const ONE_SECOND = 1000;
const THIRTY_SECONDS = 30 * ONE_SECOND;

// For CI, you may want to set BASE_URL to the deployed application.
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
const baseURL = process.env.NX_PUBLIC_SERVER_URL || 'http://localhost:3333';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
// Ensure tests run via VSCode debugger are run from the root of the repo
if (process.cwd().endsWith('/apps/jetstream-e2e')) {
process.chdir('../../');
}

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */

use: {
actionTimeout: THIRTY_SECONDS,
navigationTimeout: THIRTY_SECONDS,
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
permissions: ['notifications'],
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn nx serve jetstream-web-extension',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
},
// webServer: {
// command: 'yarn nx serve jetstream-web-extension',
// url: 'http://localhost:4200',
// reuseExistingServer: !process.env.CI,
// cwd: workspaceRoot,
// },
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
name: 'setup',
testMatch: /.*\.setup\.ts/,
use: {
...devices['Desktop Chrome'],
},
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

// Uncomment for mobile browsers support
/* {
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
}, */

// Uncomment for branded browsers
/* {
name: 'Microsoft Edge',
use: { ...devices['Desktop Edge'], channel: 'msedge' },
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Setup is giving all sorts of issues - login state is not being saved consistently
// storageState: 'playwright/.auth/web-ext-user.json',
},
testMatch: /.*\.spec\.ts/,
// dependencies: ['setup'],
},
{
name: 'Google Chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
} */
],
});
1 change: 1 addition & 0 deletions apps/jetstream-web-extension-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"projectType": "application",
"sourceRoot": "apps/jetstream-web-extension-e2e/src",
"implicitDependencies": ["jetstream-web-extension"],
"tags": ["scope:e2e"],
"targets": {
"e2e": {
"executor": "@nx/playwright:playwright",
Expand Down
Loading

0 comments on commit e1026fb

Please sign in to comment.